Architecture Components ViewModels inject with dagger with params that come from the Activity/Fragment

1.5k views Asked by At

I'm trying to add the new Architecture Components ViewModel to my application while injecting them with dagger. I based my code on what google showed here. I'm trying to avoid having a ViewModelFactory for each ViewModel type, so I used the ViewModelFactory that receives Map<Class<? extends ViewModel>, Provider<ViewModel>> creators. It works for ViewModels that have dependencies with @Singleton scope. However, one of my ViewModels has a dependency that comes from the fragment. This is the module of that fragment:

@Module
public abstract class DownloadIssueDialogFragmentModule {

    @Binds
    abstract DialogFragment dialogFragment(DownloadIssueDialogFragment dialogFragment);

    @Provides
    @FragmentScope
    static Issue provideIssue(DownloadIssueDialogFragment dialogFragment) {
        return dialogFragment.getIssue();
    }
}

And my ViewModelModule:

@Module
public abstract class ViewModelModule {

    @Binds
    abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);

    @Binds
    @IntoMap
    @ViewModelKey(DownloadIssueViewModel.class)
    abstract ViewModel bindDownloadIssueViewModel(DownloadIssueViewModel viewModel);

}

dagger says it can't provide Issue. It makes sense since Map<Class<? extends ViewModel>, Provider<ViewModel>> seems to be created at compile time. But I will only know the parameter during the scope of that fragment. How can I achieve this?

Thank you.

EDIT:

In the end I went with a different approach. Now I create a factory for each ViewModel and instead of injecting ViewModels, I inject the factory.

I created this library: AutoViewModelFactory

To automatically generate the factories. It's the best solution I've found so far.

1

There are 1 answers

2
David Rawson On

Since Android Architecture Component ViewModels have a greater scope (read more persistent lifecycle) than Fragments you should avoid making the ViewModel depend on the field from the Fragment.

However, if the Issue is only known at runtime and is generated by logic in the Fragment you may be able to escape the small dependency cycle issue by using the Holder pattern.

This has been discussed in some other Dagger 2 StackOverflow questions but you would simply define a Java bean with public accessors/mutators:

class IssueHolder {
    private Issue issue;

    @Inject
    IssueHolder() {} //empty explicit constructor as required by Dagger 2         

    public void setIssue(@Nullable Issue issue) { 
        this.issue = issue;
    }

    @Nullable
    public Issue getIssue() {
        return issue;
    }
}

Then you can make your ViewHolder depend on IssueHolder rather than directly on Issue:

@Inject IssueHolder issueHolder;

public void doSomething() {
    if (issueHolder.get() == null) {
        throw new IllegalStateException("Expected IssueHolder to be set by IssueFragment at this point");
    }
    //TODO: the logic you want here
}

As like any pattern, this Holder pattern should be used sparingly as it can easily degenerate. The best solution is, if possible, to design your modules and dependencies in such a way so as to eliminate the possibility of cycles,