带有导航图事务的 Fragment InstantiationException

FragmentInstantiationException with Navigation Graph transaction

我在尝试通过 Android 导航组件(导航图)执行事务时遇到异常。但是它在没有导航图的情况下使用旧的 fragent 事务方式工作正常!

 java.lang.RuntimeException: Unable to start activity ComponentInfo{XYZ/XYZ.framework.presentation.MainActivity}: android.view.InflateException: Binary XML file line #18 in XYZ:layout/activity_main: Binary XML file line #18 in XYZ:layout/activity_main: Error inflating class androidx.fragment.app.FragmentContainerView
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3449)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: android.view.InflateException: Binary XML file line #18 in XYZ:layout/activity_main: Binary XML file line #18 in com.xyz.diary:layout/activity_main: Error inflating class androidx.fragment.app.FragmentContainerView
     Caused by: android.view.InflateException: Binary XML file line #18 XYZ:layout/activity_main: Error inflating class androidx.fragment.app.FragmentContainerView
     Caused by: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment XYZ.recipeList.RecipeListFragment: could not find Fragment constructor
        at androidx.fragment.app.Fragment.instantiate(Fragment.java:628)
        at androidx.fragment.app.FragmentContainer.instantiate(FragmentContainer.java:57)
        at androidx.fragment.app.FragmentManager.instantiate(FragmentManager.java:483)
        at androidx.navigation.fragment.FragmentNavigator.instantiateFragment(FragmentNavigator.java:132)
        at androidx.navigation.fragment.FragmentNavigator.navigate(FragmentNavigator.java:162)
        at androidx.navigation.fragment.FragmentNavigator.navigate(FragmentNavigator.java:58)
        at androidx.navigation.NavGraphNavigator.navigate(NavGraphNavigator.java:71)
        at androidx.navigation.NavGraphNavigator.navigate(NavGraphNavigator.java:28)
        at androidx.navigation.NavController.navigate(NavController.java:1066)
        at androidx.navigation.NavController.onGraphCreated(NavController.java:639)
        at androidx.navigation.NavController.setGraph(NavController.java:592)
        at androidx.navigation.NavController.setGraph(NavController.java:557)
        at androidx.navigation.NavController.setGraph(NavController.java:539)
        at androidx.navigation.fragment.NavHostFragment.onCreate(NavHostFragment.java:248)
        at androidx.fragment.app.Fragment.performCreate(Fragment.java:2949)
        at androidx.fragment.app.FragmentStateManager.create(FragmentStateManager.java:475)
        at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:278)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2106)
        at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:1971)
        at androidx.fragment.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:311)
        at androidx.fragment.app.FragmentContainerView.<init>(FragmentContainerView.java:180)
        at androidx.fragment.app.FragmentLayoutInflaterFactory.onCreateView(FragmentLayoutInflaterFactory.java:52)
        at androidx.fragment.app.FragmentController.onCreateView(FragmentController.java:135)

nav_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph"
    app:startDestination="@id/recipeListFragment">

    <fragment
        android:id="@+id/recipeListFragment"
        android:name="com.xyz.diary.framework.presentation.recipeList.RecipeListFragment"
        android:label="RecipeListFragment" >
        <action
            android:id="@+id/action_recipeListFragment_to_recipeInsertNewFragment"
            app:destination="@id/recipeInsertNewFragment" />
        <action
            android:id="@+id/action_recipeListFragment_to_recipeDetailFragment"
            app:destination="@id/recipeDetailFragment" />
    </fragment>
</navigation>

activty_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".framework.presentation.MainActivity">
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/container"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>

RecipeListFragment

@FlowPreview
@ExperimentalCoroutinesApi
class RecipeListFragment
constructor(
    private val viewModelFactory: ViewModelProvider.Factory,
    private val dateUtil: DateUtil
) : Fragment() {

    val viewModel: RecipeListViewModel by viewModels {
        viewModelFactory
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.setupChannel()
    }

    @ExperimentalComposeUiApi
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return ComposeView(requireContext()).apply {
            setContent { BoxLayout() }
        }
    }
...
}

HILT DI

@ExperimentalCoroutinesApi
@FlowPreview
@Module
@InstallIn(SingletonComponent::class)
object RecipeViewModelModule {

    @Singleton
    @JvmStatic
    @Provides
    fun provideRecipeViewModelFactory(
        recipeListInteractors: RecipeListInteractors,
        recipeInsertNewInteractors: RecipeInsertNewInteractors,
        recipeDetailInteractors: RecipeDetailInteractors,
        recipeFactory: RecipeFactory,
        editor: SharedPreferences.Editor,
        sharedPreferences: SharedPreferences
    ): ViewModelProvider.Factory {
        return RecipeViewModelFactory(
            recipeListInteractors = recipeListInteractors,
            recipeInsertNewInteractors=recipeInsertNewInteractors,
            recipeDetailInteractors = recipeDetailInteractors,
            recipeFactory = recipeFactory,
            editor = editor,
            sharedPreferences = sharedPreferences
        )
    }

}

@ExperimentalCoroutinesApi
@FlowPreview
@InstallIn(SingletonComponent::class)
@Module
object AppModule {


    // https://developer.android.com/reference/java/text/SimpleDateFormat.html?hl=pt-br
    @JvmStatic
    @Singleton
    @Provides
    fun provideDateFormat(): SimpleDateFormat {
        val sdf = SimpleDateFormat("yyyy-MM-dd hh:mm:ss a", Locale.ENGLISH)
        sdf.timeZone = TimeZone.getTimeZone("UTC-7") // match firestore
        return sdf
    }

    @JvmStatic
    @Singleton
    @Provides
    fun provideDateUtil(dateFormat: SimpleDateFormat): DateUtil {
        return DateUtil(
            dateFormat
        )
    }

docs所述:

All subclasses of Fragment must include a public no-argument constructor. The framework will often re-instantiate a fragment class when needed, in particular during state restore, and needs to be able to find this constructor to instantiate it. If the no-argument constructor is not available, a runtime exception will occur in some cases during state restore.

您的片段 class 应该有一个 public 无参数构造函数,以便由 Android 实例化(当进程被系统终止时)或用于导航组件等

尝试将您的 XMLFragmentContainerView 更改为 fragment,如下所示:

@AndroidEntryPoint
class MainNavHostFragment : NavHostFragment() {
    @Inject
    lateinit var mainFragmentFactory: RecipeFragmentFactory

    override fun onAttach(context: Context) {
        super.onAttach(context)
        childFragmentManager.fragmentFactory = mainFragmentFactory
    }
}

xml

    <fragment
    android:id="@+id/container"
    android:name="<path>.MainNavHostFragment"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:defaultNavHost="true"
    app:navGraph="@navigation/nav_graph" />