Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to instantiate fragment, could not find Fragment constructor #1

Open
Berki2021 opened this issue Aug 20, 2021 · 3 comments
Open

Comments

@Berki2021
Copy link

Hello sir, I tried to implement your "improved fragmentfactory" as you had shown here.

The problem I have is, that I get the following error:

androidx.fragment.app.Fragment$InstantiationException: **Unable to instantiate fragment com.example0.app.presentation.documents.DocumentsFragment: could not find Fragment constructor**
        at androidx.fragment.app.FragmentFactory.instantiate(FragmentFactory.java:131)
        at com.example0.app.presentation.util.fragment.MainFragmentFactory.instantiate(FragmentFactory.kt:78)
        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.NavController.navigate(NavController.java:1066)
        at androidx.navigation.NavController.navigate(NavController.java:944)
        at androidx.navigation.NavController.navigate(NavController.java:877)
        at androidx.navigation.ui.NavigationUI.onNavDestinationSelected(NavigationUI.java:97)
        at androidx.navigation.ui.NavigationUI$5.onNavigationItemSelected(NavigationUI.java:531)
        at com.google.android.material.navigation.NavigationBarView$1.onMenuItemSelected(NavigationBarView.java:241)
        at androidx.appcompat.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:834)
        at androidx.appcompat.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:158)
        at androidx.appcompat.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:985)
        at com.google.android.material.navigation.NavigationBarMenuView$1.onClick(NavigationBarMenuView.java:110)
        at android.view.View.performClick(View.java:7125)
        at android.view.View.performClickInternal(View.java:7102)
        at android.view.View.access$3500(View.java:801)
        at android.view.View$PerformClick.run(View.java:27336)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
     Caused by: java.lang.NoSuchMethodException: com.example0.app.presentation.documents.DocumentsFragment.<init> []
        at java.lang.Class.getConstructor0(Class.java:2332)
        at java.lang.Class.getConstructor(Class.java:1728)
        at androidx.fragment.app.FragmentFactory.instantiate(FragmentFactory.java:121)
        at com.example0.app.presentation.util.fragment.MainFragmentFactory.instantiate(FragmentFactory.kt:78) 
        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.NavController.navigate(NavController.java:1066) 
        at androidx.navigation.NavController.navigate(NavController.java:944) 
        at androidx.navigation.NavController.navigate(NavController.java:877) 
        at androidx.navigation.ui.NavigationUI.onNavDestinationSelected(NavigationUI.java:97) 
        at androidx.navigation.ui.NavigationUI$5.onNavigationItemSelected(NavigationUI.java:531) 
        at com.google.android.material.navigation.NavigationBarView$1.onMenuItemSelected(NavigationBarView.java:241) 
        at androidx.appcompat.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:834) 
        at androidx.appcompat.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:158) 
        at androidx.appcompat.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:985) 
        at com.google.android.material.navigation.NavigationBarMenuView$1.onClick(NavigationBarMenuView.java:110) 
        at android.view.View.performClick(View.java:7125) 
        at android.view.View.performClickInternal(View.java:7102) 
        at android.view.View.access$3500(View.java:801) 
        at android.view.View$PerformClick.run(View.java:27336) 
        at android.os.Handler.handleCallback(Handler.java:883) 
        at android.os.Handler.dispatchMessage(Handler.java:100) 
        at android.os.Looper.loop(Looper.java:214) 
        at android.app.ActivityThread.main(ActivityThread.java:7356) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 

Here are my fragments:

DocumentFragment

@AndroidEntryPoint
class DocumentsFragment @Inject constructor(
    private val fileValidator: FileValidator,
    private val snackbarUtils: InternetErrorSnackbar,
) : BusEventFragment<FragmentDocumentsBinding>(), SwipeRefreshLayout.OnRefreshListener {
    private val documentViewModel: DocumentViewModel by viewModels()
    @Inject
    lateinit var documentListAdapter: DocumentListAdapter

    override val bindingInflater: (LayoutInflater) -> FragmentDocumentsBinding
        get() = FragmentDocumentsBinding::inflate

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        bindObjects()
    }

    private fun bindObjects() {
        with(binding) {
            adapter = documentListAdapter
            refreshListener = this@DocumentsFragment
        }
    }

    override val onDestroyView: () -> Unit
        get() = {
            snackbarUtils.onDestroyView()
            binding.rvDocuments.adapter = null
        }
}

When I delete my dependencies from my fragment-constructor, everything works fine. Does that mean, that we are not able to constructor inject with your "improved" method??

@Berki2021
Copy link
Author

Okay, I found the actual problem. It has nothing to do with constructor injecting my fragments, but with my base class BusEventFragment. Because loadFragmentClass tries to load a fragment of type androidx.fragment.Fragment and not my special desired BusEventFragment, this does not work. I will try to work around and do this with my custom class

@Berki2021
Copy link
Author

Edit

Your FragmentFactory has one big flaw: If one wants to inject dependencies that need a fragment instance, it is not possible, because you are installing your FragmentFactoryModule inside SingletonComponent::class and therefore you can't get lifecycleaware dependencies into your fragments, because they have to be injected inside FragmentComponent::class

@Berki2021
Copy link
Author

Berki2021 commented Aug 20, 2021

Okay, here is the final fix for the problem and the correct way to instantiate all objects:

FragmentFactoryModule

@Module
@InstallIn(FragmentComponent::class) // NOT SINGLETONCOMPONENT::CLASS
abstract class FragmentFactoryModule {
    @Binds
    @IntoMap
    @FragmentKey(FirstFragment::class)
    abstract fun bindFirstFragment(fragment: FirstFragment): Fragment

    @Binds
    @IntoMap
    @FragmentKey(SecondFragment::class)
    abstract fun bindSecondFragment(fragment: SecondFragment): Fragment

    @Binds
    @IntoMap
    @FragmentKey(ThirdFragment::class)
    abstract fun bindThirdFragmentt(fragment: ThirdFragment): Fragment
}

@MapKey
@Retention(AnnotationRetention.RUNTIME)
annotation class FragmentKey(val value: KClass<out Fragment>)

NavHostModule

@Module
@InstallIn(FragmentComponent::class) // Since fragments are only available in FragmentComponent::class
abstract class NavHostModule {
    @Binds
    abstract fun provideNavHostFragment(fragment: MainFragmentFactory): FragmentFactory
}

FragmentFactoryProviderModule

@Module
@InstallIn(FragmentComponent::class)
object FragmentModule {

    @Provides
    fun provideMainFragmentFactory(providerMap: Map<Class<out Fragment>, @JvmSuppressWildcards Provider<Fragment>>)
        = MainFragmentFactory(providerMap)
}

Fragment Factory

@Singleton
class MainFragmentFactory (
    private val providerMap: Map<Class<out Fragment>, @JvmSuppressWildcards Provider<Fragment>>
) : FragmentFactory() {
    override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
        val fragmentClass = loadFragmentClass(classLoader, className)

        val creator = providerMap[fragmentClass] ?: providerMap.entries.firstOrNull {
            fragmentClass.isAssignableFrom(it.key)
        }?.value

        return creator?.get() ?: super.instantiate(classLoader, className)
    }
}

NavHostFragment

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant