I want to understand why I have dagger cycle dependency here:
class MachineFragment : Fragment() {
@Inject
lateinit var viewModelFactory: ViewModelFactory
private lateinit var viewModel: MachinesViewModel
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
super.onCreateView(inflater, container, savedInstanceState)
viewModel = ViewModelProvider(this, viewModelFactory).get(MachinesViewModel::class.java)
return inflater.inflate(R.layout.fragment_machines, container, false)
}
@Singleton
@Component(
dependencies = [],
modules = [
AppModule::class,
TestViewModelModule::class,
InjectorModule::class
]
)
interface AppComponent {
fun inject(subject: AppApplication)
public abstract class TestViewModelModule {
@Binds
public abstract ViewModelFactory bindViewModelFactory(ViewModelFactory viewModelFactory);
/*
you have to explicitly define ViewModel instead of concrete
implementation to avoid cycle dependency error
*/
@Binds
@IntoMap
@ViewModelKey(MachinesViewModel.class)
abstract ViewModel bindMachinesViewModel(MachinesViewModel vm);
}
public class ViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels;
@Inject
public ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels) {
this.viewModels = viewModels;
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
Provider<ViewModel> viewModelProvider = viewModels.get(modelClass);
if (viewModelProvider == null) {
throw new IllegalArgumentException("model class " + modelClass + " not found");
}
return (T) viewModelProvider.get();
}
}
However when I define the same injection field in MainActivity, cycle dependency disappear:
class MainActivity : AppCompatActivity() {
@Inject
lateinit var viewModelFactory: ViewModelFactory
private lateinit var viewModel: MachinesViewModel
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/* start destination in navigation.xml defines entry point */
viewModel = ViewModelProvider(this, viewModelFactory).get(MachinesViewModel::class.java)
}
I already have solved this issue by adding @Named("machine-vm-factory") annotation, but the root cause is unclear for me. I will appreciate your thoughts on this. Thank you!
I think the problem is this line in
TestViewModelModule:Here you're asking Dagger to give you a
ViewModelFactorywhenever anyone requests aViewModelFactory, and since@Bindshas a higher priority than@Inject, Dagger doesn't really know how to construct aViewModelFactory, hence the cycle.Adding qualifier changes the key:
This means that when anyone is asking for a
@Named("machine-vm-factory") ViewModelFactory, Dagger will give us aViewModelFactoryusing the@Injectannotated constructor, so the cycle is gone.See https://dagger.dev/semantics/#keys