52

I have a ViewModel class just like the one defined in the Connecting ViewModel and repository section of Architecture guide. When I run my app I get a runtime exception. Does anyone know how to get around this? Should I not be injecting the ViewModel? Is there a way to tell the ViewModelProvider to use Dagger to create the model?

public class DispatchActivityModel extends ViewModel {

    private final API api;

    @Inject
    public DispatchActivityModel(API api) {
        this.api = api;
    }
}

Caused by: java.lang.InstantiationException: java.lang.Class has no zero argument constructor at java.lang.Class.newInstance(Native Method) at android.arch.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:143) at android.arch.lifecycle.ViewModelProviders$DefaultFactory.create(ViewModelProviders.java:143)  at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:128)  at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:96)  at com.example.base.BaseActivity.onCreate(BaseActivity.java:65)  at com.example.dispatch.DispatchActivity.onCreate(DispatchActivity.java:53)  at android.app.Activity.performCreate(Activity.java:6682)  at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2619) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2727)  at android.app.ActivityThread.-wrap12(ActivityThread.java)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1478)  at android.os.Handler.dispatchMessage(Handler.java:102)  at android.os.Looper.loop(Looper.java:154)  at android.app.ActivityThread.main(ActivityThread.java:6121)

Brandon Minnick
  • 13,342
  • 15
  • 65
  • 123
TheHebrewHammer
  • 3,018
  • 3
  • 28
  • 45

7 Answers7

99

You need to implement your own ViewModelProvider.Factory. There is an example app created by Google demonstrating how to connect Dagger 2 with ViewModels. LINK. You need those 5 things:

In ViewModel:

@Inject
public UserViewModel(UserRepository userRepository, RepoRepository repoRepository) {

Define annotation:

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
@interface ViewModelKey {
    Class<? extends ViewModel> value();
}

In ViewModelModule:

@Module
abstract class ViewModelModule {
    @Binds
    @IntoMap
    @ViewModelKey(UserViewModel.class)
    abstract ViewModel bindUserViewModel(UserViewModel userViewModel);

In Fragment:

@Inject
ViewModelProvider.Factory viewModelFactory;

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            userViewModel = ViewModelProviders.of(this, viewModelFactory).get(UserViewModel.class);

Factory:

@Singleton
public class GithubViewModelFactory implements ViewModelProvider.Factory {
    private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;

    @Inject
    public GithubViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
        this.creators = creators;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        Provider<? extends ViewModel> creator = creators.get(modelClass);
        if (creator == null) {
            for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
                if (modelClass.isAssignableFrom(entry.getKey())) {
                    creator = entry.getValue();
                    break;
                }
            }
        }
        if (creator == null) {
            throw new IllegalArgumentException("unknown model class " + modelClass);
        }
        try {
            return (T) creator.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
MyDogTom
  • 4,486
  • 1
  • 28
  • 41
Robert Wysocki
  • 1,105
  • 8
  • 6
  • 6
    Welcome to Stack Overflow. A link to a potential solution is always welcome, but please add context around the link so your fellow users will have some idea what it is and why it’s there. Always quote the most relevant part of an important link, in case the target site is unreachable or goes permanently offline. Take a look at [Why and how are some answers deleted?](https://stackoverflow.com/help/deleted-answers) and [How do I write a good answer?](https://stackoverflow.com/help/how-to-answer) – Gary99 Jun 12 '17 at 19:07
  • 5
    You also need to define @ViewModelKey https://github.com/googlesamples/android-architecture-components/blob/388a10dd5a814ba6aaa9bf8dee8e7c1c5840b3a5/GithubBrowserSample/app/src/main/java/com/android/example/github/di/ViewModelKey.java – nmu Jul 14 '17 at 10:13
  • 9
    You also have to declare: `@Binds abstract ViewModelProvider.Factory bindViewModelFactory(GithubViewModelFactory factory);` in your ViewModelModule – Andrzej Sawoniewicz Aug 21 '17 at 07:37
  • 1
    this defeats the purpose of using dagger as you are still initialising the viewModel object manually using the viewModelProvider. – Jono Feb 20 '18 at 10:13
  • Where do we use `GithubViewModelFactory `? – Levon Petrosyan Jul 06 '18 at 12:55
  • 1
    @LevonPetrosyan You `@Inject` it into your `Activity`... But to do that you need to register it with a `@Module` (preferably the `ViewModelModule`) That way you get an instance of `ViewModelProvider.Factory` which you can then pass to `ViewModelProviders.of(...)` – riyaz-ali Jul 27 '18 at 12:25
  • @jonney It doesn't! You are not _really instantiating_ the `ViewModel` but rather asking the `Provider` for an instance of it and this way Dagger still resolves all the dependencies of the `ViewModel` – riyaz-ali Jul 27 '18 at 12:27
  • @AndrzejSawoniewicz why does he need to declare: `@Binds abstract ViewModelProvider.Factory bindViewModelFactory(GithubViewModelFactory factory);` in his `ViewModelModule` if it works without this statement? – Lucas Santos Nov 29 '18 at 06:18
  • @LucasSantos It is the old topic my memory is not that good :) I remember, however, that it did not work without that. – Andrzej Sawoniewicz Nov 30 '18 at 10:32
  • @AndrzejSawoniewicz I see. Thanks for answering. But now it works without that. Dagger generates the Factory even if you don't bind it. I'm not sure why... – Lucas Santos Nov 30 '18 at 17:14
  • 2
    I believe there are a few things missing in this answer. I still get my factory being null and Android falls back to default Factory and produces 'no zero argument constructor' exception. – Slobodan Antonijević Mar 05 '19 at 09:57
27

Today I learnt a way to avoid having to write factories for my ViewModel classes:

class ViewModelFactory<T : ViewModel> @Inject constructor(
    private val viewModel: Lazy<T>
) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T = viewModel.get() as T
}

EDIT: As pointed out by @Calin in the comments, we are using Dagger's Lazy in the code snippet above, not Kotlin's.

Rather than injecting the ViewModel, you can inject a generic ViewModelFactory into your activities and fragments and obtain an instance of any ViewModel:

class MyActivity : Activity() {

    @Inject
    internal lateinit var viewModelFactory: ViewModelFactory<MyViewModel>
    private lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        AndroidInjection.inject(this)
        super.onCreate(savedInstanceState)
        this.viewModel = ViewModelProviders.of(this, viewModelFactory)
            .get(MyViewModel::class.java)
        ...
    }

    ...
}

I used AndroidInjection.inject(this) as with the dagger-android library, but you can inject your activity or fragment the way you prefer. All that is left is to make sure you provide your ViewModel from a module:

@Module
object MyModule {
    @JvmStatic
    @Provides
    fun myViewModel(someDependency: SomeDependency) = MyViewModel(someDependency)
} 

Or applying the @Inject annotation to its constructor:

class MyViewModel @Inject constructor(
    someDependency: SomeDependency
) : ViewModel() {
    ...
}
argenkiwi
  • 2,134
  • 1
  • 23
  • 26
  • 2
    inline fun ViewModelFactory.get(activity:FragmentActivity): T = ViewModelProviders.of(activity, this).get(T::class.java) – nxdrvr Mar 01 '18 at 10:39
  • Not clear to me why you do a have Lazi in the factory controller? – Calin Mar 27 '18 at 12:42
  • 3
    Ah got it, is Lazy from dagger, not lazy from kotlin :( – Calin Mar 27 '18 at 12:51
  • Seems the `get()` is a unresolved reference in Android Studio even we have that T:ViewModel :( – Albert Gao Apr 18 '18 at 22:57
  • 1
    @AlbertGao, have you made sure you are referencing Dagger's `Lazy` instead of Kotlin's, as @cain pointed out? – argenkiwi Apr 19 '18 at 03:03
  • 1
    @argenkiwi My bad. after adding `import dagger.Lazy`, all works now, really thanks! – Albert Gao Apr 19 '18 at 03:13
  • I don't understand why this is not the most upvoted answer, it seems the most elegant, or maybe I'm missing something. Works great anyway thx :) – lelloman Jun 10 '18 at 12:49
  • rectifying, now I got the pitfall of this solution, it won't work if you have BaseViewModel class that extends ViewModel and that is extended by your actual view models :\ – lelloman Jun 19 '18 at 08:21
  • @lelloman I'm having this issues as well. Were you able to figure out a solution for this? – huey77 Jul 17 '18 at 17:12
  • @huey77 yes I did make it work but not with the approach suggested in this answer, something like [this other answer](https://stackoverflow.com/a/44506312/1527232) but I had to add a few things. I made a gist with (I think) all the parts [here](https://gist.github.com/lelloman/aa61bc084e9a0fadc33a35aadcf0cbda) – lelloman Jul 17 '18 at 18:45
  • Remember to past your activity and not your fragment in the ViewModelsProviders.of() – Emmanuel Guerra Aug 10 '19 at 17:47
4

I believe there is a second option if you don't want to use the factory mentioned in Robert's answer. It is not necessarily better solution but it is always good to know the options.

You can leave your viewModel with default constructor and inject your dependencies just as you do in case of activities or other elements created by system. Example:

ViewModel:

public class ExampleViewModel extends ViewModel {

@Inject
ExampleDependency exampleDependency;

public ExampleViewModel() {
    DaggerExampleComponent.builder().build().inject(this);
    }
}

Component:

@Component(modules = ExampleModule.class)
public interface ExampleComponent {

void inject(ExampleViewModel exampleViewModel);

}

Module:

@Module
public abstract class ExampleModule {

@Binds
public abstract ExampleDependency bindExampleDependency(ExampleDependencyDefaultImplementation exampleDependencyDefaultImplementation);

}

Cheers, Piotr

  • 1
    But this didn't solve how to initialize the ViewModel with mock dependency in Unit test – Simon K. Gerges Nov 10 '17 at 19:44
  • If you look at the [Guide to App Architecture](https://developer.android.com/topic/libraries/architecture/guide.html) that's actually how they seem to do it (there's an `@Inject` within a `ViewModel`). – Fred Porciúncula Apr 25 '18 at 04:14
4

What may not be obvious in the question is that the ViewModel cannot be injected that way because the ViewModelProvider default Factory that you get from the

ViewModelProvider.of(LifecycleOwner lo) 

method with only the LifecycleOwner parameter can only instantiate a ViewModel that has a no-arg default constructor.

You have a param: 'api' in your constructor:

public DispatchActivityModel(API api) {

In order to do that you need to create a Factory so that you can tell it how to create itself. The sample code from google gives you Dagger config and Factory code as mentioned in the accepted answer.

DI was created to avoid use of the new() operator on dependencies because if the implementations change, every reference would have to change as well. The ViewModel implementation wisely uses a static factory pattern already with ViewProvider.of().get() which makes its injection unnecessary in the case of no-arg constructor. So in the case you don't need to write the factory you don't need to inject a factory of course.

Droid Teahouse
  • 893
  • 8
  • 15
3

I'd like to provide a third option for anyone stumbling upon this question. The Dagger ViewModel library will allow you to inject in a Dagger2-like way with ViewModels optionally specifying the ViewModel's scope.

It removes a lot of the boilerplate and also allows the ViewModels to be injected in a declarative way using an annotation:

@InjectViewModel(useActivityScope = true)
public MyFragmentViewModel viewModel;

It also requires a small amount of code to setup a module from which the fully dependency injected ViewModels can be generated and after that it's as simple as calling:

void injectFragment(Fragment fragment, ViewModelFactory factory) {
    ViewModelInejectors.inject(frag, viewModelFactory);
}

On the ViewModelInjectors class which is generated.

DISCLAIMER: it is my library but I believe it's also of use to the author of this question and anyone else wanting to achieve the same thing.

onepointsixtwo
  • 108
  • 1
  • 8
0

The default ViewModel factory used to get an instance of your DispatchActivityModel in your view constructs the ViewModels using assumed empty constructors.

You can write your custom ViewModel.Factory to get around it, but you'd stil need to take care of completing the dependency graph yourself if you want to provide your API class.

I wrote a small library that should make overcoming this common more straightforward and way cleaner, no multibindings or factory boilerplate needed, while also giving the ability to further parametrise the ViewModel at runtime: https://github.com/radutopor/ViewModelFactory

@ViewModelFactory
public class DispatchActivityModel extends ViewModel {

    private final API api;

    public DispatchActivityModel(@Provided API api) {
        this.api = api;
    }
}

In the view:

public class DispatchActivity extends AppCompatActivity {
    @Inject
    private DispatchActivityModelFactory2 viewModelFactory;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        appComponent.inject(this);

        DispatchActivityModel viewModel = ViewModelProviders.of(this, viewModelFactory.create())
            .get(UserViewModel.class)
    }
}

Like I mentioned, you can also easily add runtime parameters to your ViewModel instances as well:

@ViewModelFactory
public class DispatchActivityModel extends ViewModel {

    private final API api;
    private final int dispatchId;

    public DispatchActivityModel(@Provided API api, int dispatchId) {
        this.api = api;
        this.dispatchId = dispatchId;
    }
}

public class DispatchActivity extends AppCompatActivity {
    @Inject
    private DispatchActivityModelFactory2 viewModelFactory;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        appComponent.inject(this);

        final int dispatchId = getIntent().getIntExtra("DISPATCH_ID", -1);
        DispatchActivityModel viewModel = ViewModelProviders.of(this, viewModelFactory.create(dispatchId))
            .get(UserViewModel.class)
    }
}
Radu Topor
  • 1,504
  • 1
  • 11
  • 12
0

Recently I've found another elegant solution for this problem.

My fragment with injected ViewModel looks like:

class SettingsFragment : Fragment() {

    private val viewModel by viewModels(DI::settingsViewModel)
}

To achieve this I've created custom by viewModels delegate:

inline fun <reified VM : ViewModel> Fragment.viewModels(
    crossinline viewModelProducer: () -> VM
): Lazy<VM> {
    return lazy(LazyThreadSafetyMode.NONE) { createViewModel { viewModelProducer() } }
}


inline fun <reified VM : ViewModel> Fragment.createViewModel(
    crossinline viewModelProducer: () -> VM
): VM {
    val factory = object : ViewModelProvider.Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <VM : ViewModel> create(modelClass: Class<VM>) = viewModelProducer() as VM
    }
    val viewModelProvider = ViewModelProvider(this, factory)
    return viewModelProvider[VM::class.java]
}

This property delegate expects lambda function that could create ViewModel instance as argument.

And we can provide this lambda using Dagger2 like this:

@Component(
    modules = [MyModule::class]
)
interface MyComponent {
    //1) Declare function that provides our ViewModel in Component
    fun settingsViewModel(): SettingsViewModel
}


@Module
abstract class MyModule {
    @Module
    companion object {

        //2) Create provides method than provides our ViewModel in Module
        @Provides
        @JvmStatic
        fun provideSettingsViewModel(
            ... // Pass your ViewModel dependencies
        ): SettingsViewModel {
            return SettingsViewModel(
                ...// Pass your ViewModel dependencies
            )
        }
    }
}

// 3) Build Component somewhere, for example in singleton-object.
object DI {

    private var component: MyComponent by lazy {
        MyComponent.builder().build()
    }

    // 4) Declare method that delegates ViewModel creation to Component
    fun settingsViewModel() = component.settingsViewModel()
}

And finally we can pass DI.settingsViewModel() method reference to our delegate in fragment:

private val viewModel by viewModels(DI::settingsViewModel)
ZSergei
  • 807
  • 10
  • 18