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
:
@Binds
public abstract ViewModelFactory bindViewModelFactory(ViewModelFactory viewModelFactory);
Here you're asking Dagger to give you a ViewModelFactory
whenever anyone requests a ViewModelFactory
, and since @Binds
has a higher priority than @Inject
, Dagger doesn't really know how to construct a ViewModelFactory
, hence the cycle.
Adding qualifier changes the key:
@Binds
@Named("machine-vm-factory")
public abstract ViewModelFactory bindViewModelFactory(ViewModelFactory viewModelFactory);
This means that when anyone is asking for a @Named("machine-vm-factory") ViewModelFactory
, Dagger will give us a ViewModelFactory
using the @Inject
annotated constructor, so the cycle is gone.