Open matpag opened 5 years ago
The error seems to suggest your Dagger setup is incorrect -- did you change anything with the setup there? Also, I believe these calls will be triggered after onCreate
finishes, meaning your custom FragmentFactory
will not be injected and set before your Activity
has called super.onCreate(savedInstanceState)
. This will break the restoration of your Fragments during configuration changes or process death, as the FragmentFactory
will not be set before the fragments are created.
This is my Application class:
class AppApplication : Application(), HasActivityInjector {
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
AppInjector.init(this)
}
override fun activityInjector() = dispatchingAndroidInjector
}
AppComponent
@Singleton
@Component(
modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
MainActivityModule::class
]
)
interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: AppApplication)
}
FragmentFactory
@PerActivity
class DefaultFragmentFactory @Inject constructor(
private val creators: Map<Class<out Fragment>, @JvmSuppressWildcards Provider<Fragment>>
) : FragmentFactory() {
override fun instantiate(classLoader: ClassLoader, className: String, args: Bundle?): Fragment {
val fragmentClass = loadFragmentClass(classLoader, className)
val creator = creators[fragmentClass]
?: throw java.lang.IllegalArgumentException("Unknown fragment class $fragmentClass")
try {
val fragment = creator.get()
fragment.arguments = args
return fragment
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
FragmentModule
@Module
abstract class FragmentModule {
@Binds
@IntoMap
@FragmentKey(SearchFragment::class)
abstract fun bindSearchFragment(searchFragment: SearchFragment): Fragment
@Binds
abstract fun bindFragmentFactory(factory: DefaultFragmentFactory): FragmentFactory
}
ActivityModule
@Suppress("unused")
@Module
abstract class MainActivityModule {
@PerActivity
@ContributesAndroidInjector(modules = [FragmentModule::class])
abstract fun contributeMainActivity(): MainActivity
}
I omitted AppModule because I don't think the problem can reside there.
Sincerely I don't know what I'm doing wrong... Probably it's my fault because I don't have a big experience with Dagger
You have no multibinding mapping for MainFragment
in your FragmentModule
, only for SearchFragment
-- this is causing your error.
Can you help me a bit more Alex? What should I add in FragmentModule
?
Thank you
I think I've found the problem. Now is working fine. The only problem that I still have (even with your example) is that if I try to use DaggerFragment
the app crash because internally it still searches for android.support.v4.app.Fragment
and not androidx.fragment.app.Fragment
so the injector doesn't recognize the correct class to inject
BTW I'm on Dagger 2.21 and still have the problem
Thanks Alex 👍
Try adding these two lines to your project's gradle.properties file:
android.useAndroidX=true
android.enableJetifier=true
Yeah, the first thing I tried following some advices found online but it's not working. I invalidated and restarted AS too without success
At the end I had to convert the library manually, I've added an answer here: https://stackoverflow.com/a/54705489/2910520
You could also try changing the version of jetifier your build is using -- add this to your root build.gradle's script:
classpath("com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta03")
I've tried but didn't work with those 2 lines:
classpath 'com.android.tools.build.jetifier:jetifier-core:1.0.0-beta03'
classpath 'com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta03'
Hi Alex, i've created a question here: https://stackoverflow.com/questions/55043890/dagger-2-how-to-bind-fragment-map-in-parent-component-from-subcomponent to be able to scope your fragment factory implementation with sub-component.
If you have time I'll be glad to have an help from you. In the question I added a link to your post because it's really useful :)
I have not worked through this scenario yet, but it is possible to have subcomponents add to the multibinding maps of their parent components. FragmentFactory
is still in alpha, but once it has been fully released Dagger itself will probably have better support for it to more easily allow for scoping like this. I believe there is already an issue for this on Dagger's Github.
Thank you Alex. I will search the issue tonight and maybe directly ask to ronshapiro for help
Currently they have deprecated the instantiate
method with the Bundle argument in fragment-ktx 1.1.0-alpha06
, so new FragmentFactory
should be like this or will crash at runtime (discovered this while updating Navigation library to 2.1.0-alpha02
)
class DefaultFragmentFactory @Inject constructor(
private val creators: Map<Class<out Fragment>, @JvmSuppressWildcards Provider<Fragment>>
) : FragmentFactory() {
//override this if you were using the bundle before or remove it if you were not
override fun instantiate(classLoader: ClassLoader, className: String, args: Bundle?): Fragment {
return instantiate(classLoader, className).apply { arguments = args }
}
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
val fragmentClass = loadFragmentClass(classLoader, className)
val found = creators.entries.find { fragmentClass.isAssignableFrom(it.key) }
val provider = found?.value
//if we don't find a match in the map, proceed with the default empty constructor, for example if used with
//navigation component this is the case of NavHostFragment or others system/libraries generated Fragments
return if (provider != null) {
provider.get()
} else {
fragmentClass.newInstance()
}
}
}
Hi, thank you for your amazing tutorial. I've tried to implement this pattern using the automatic injection took from GithubBrowserExample provided by Google. I'm referring about this class
But using this approach (which probably moved the injection after the super.onCreate call in the MainActivity) everything is broken and I get the error
Caused by: java.lang.IllegalArgumentException: No injector factory bound for Class<com.atownsend.fragmentfactorysample.ui.MainFragment>
Is there a way to let this work without manually injecting in every activity?
Thank you :)