Cannot create an instance of ViewModel class (Unable to start activity ComponentInfo)

核能气质少年 提交于 2020-01-03 09:24:19

问题


I am using MVVM, Retrofit, LiveData in my project but I get this error before that I saw these links

  • Cannot create an instance of custom ViewModel
  • Cannot create an instance of class ViewModel

Error

java.lang.RuntimeException:       
Unable to start activity ComponentInfo{ir.orangehat.movieinfo/ir.orangehat.movieinfo.application.home.HomeActivity}: java.lang.RuntimeException:

Cannot create an instance of class ir.orangehat.movieinfo.application.home.HomeViewModel

This is my ViewModel and I think the problem is in my constructor

public class HomeViewModel extends AndroidViewModel {

private MovieRepository movieRepository;

public HomeViewModel(@NonNull Application application, Context context, LifecycleOwner lifecycleOwner) {
    super(application);
    movieRepository = new MovieRepository(lifecycleOwner, context);
}

LiveData<List<Movie>> getMovies() {
    return movieRepository.getMovies();
}}

Repository

public class MovieRepository extends BaseRepository {

private LifecycleOwner lifecycleOwner;
private MovieApi movieApi;
private MovieDatabaseHelper movieDatabaseHelper;

public MovieRepository(LifecycleOwner lifecycleOwner, Context context) {
    this.lifecycleOwner = lifecycleOwner;
    movieApi = getRetrofitHelper().getService(MovieApi.class);
    movieDatabaseHelper = new MovieDatabaseHelper(context);
}

public LiveData<List<Movie>> getMovies() {
    LiveData<List<Movie>> moviesLiveData = movieApi.getMovieList();
    moviesLiveData.observe(lifecycleOwner, new Observer<List<Movie>>() {
        @Override
        public void onChanged(@Nullable List<Movie> movieArrayList) {
            movieDatabaseHelper.Save(movieArrayList);
        }
    });

    return movieDatabaseHelper.getAll();
} }

Activity class

public class HomeActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // the error is here
    HomeViewModel homeViewModel = ViewModelProviders.of(this).get(HomeViewModel.class);

    homeViewModel.getMovies().observe(HomeActivity.this, new Observer<List<Movie>>() {
        @Override
        public void onChanged(@Nullable List<Movie> movieArrayList) {
            String str = null;
            if (movieArrayList != null) {
                str = Arrays.toString(movieArrayList.toArray());
            }
            Log.e("movies", str);
        }
    });
}

}

Should i use Dagger in my project and custom factory?


回答1:


Quoting the documentation for AndroidViewModel:

Subclasses must have a constructor which accepts Application as the only parameter.

Your constructor does not meet that requirement.

Either:

  • Remove the Context context and LifecycleOwner lifecycleOwner constructor parameters from your HomeViewModel, or

  • Create a ViewModelProvider.Factory that can build your HomeViewModel instances, and use that factory with ViewModelProviders.of()




回答2:


If you are working with Kotlin and you need to inject a dependency inside your ViewModel constructor to work with like a Repository to get data (the same way we use to do with the Presenter layer) you will need to do the following.

Lets say we have a ViewModel that needs a UseCase/Interactor to be injected in the constructor to get data from.

class MainViewModel(private val itemList:ItemListUseCase):ViewModel() {
...
}

When we try to instantiate this ViewModel in our Activity/Fragment, we tend to do this

Fragment example

   class MainFragment : Fragment() {
    private lateinit var mainViewModel: MainViewModel

      override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)

            mainViewModel = requireActivity().run {
                ViewModelProviders.of(this).get(MainViewModel::class.java)
            }
          ...
        }
}

When we try to run our code, it will crash with a RuntimeException

java.lang.RuntimeException: Cannot create an instance of class com.gaston.example.viewmodel.MainViewModel ... Caused by: java.lang.InstantiationException: java.lang.Class has no zero argument constructor

This is because we are not injecting the ItemListUseCase when we instantiate our ViewModel

The first thing that comes up is to try to inject it directly from the .get() method of ViewModelProviders

ViewModelProviders.of(this).get(MainViewModel(ItemListUseCase())::class.java)

But if we do this, we will be getting the same error too.

Solution

What we need to understand is that

ViewModelProviders.of(this).get(MainViewModel::class.java)

ViewModelProviders.of() is trying to do an instance of the MainViewModel without knowing that it needs a dependency inside its constructor.

To fix this issue, we will need to let ViewModelProviers.of() know about which dependency we want to inject.

To do this, we need to create a class that will ensure that the instance of that ViewModel needs a dependency to be instantiated.

This class is called a Factory ViewModel class, since it will construct our ViewModel with the dependency it needs to work and then it will let the ViewModelProviers.of() know which dependency we need to pass to .get(MainViewModel::class.java)

class ViewModelFactory(val requestItemData: ItemListUseCase):ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return modelClass.getConstructor(ItemListUseCase::class.java).newInstance(requestItemData)
    }
}

Now, we can tell to ViewModelProviers.of() that it needs an instance of ItemListUseCase::class.java to instantiate MainViewModel(ItemListuseCase())

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        mainViewModel = requireActivity().run {
            ViewModelProviders.of(this,ViewModelFactory(ItemListUseCase())).get(MainViewModel::class.java)
        }

    }

Note that I do not pass any arguments to .get(MainViewModel::class.java) because our Factory will take care of injecting those arguments into our constructor.

At the end, if you want to avoid the Factory class, you can always use Dagger to inject your dependencies without worrying about the ViewModel Factory class.




回答3:


If it helps someone. In my case everything CommonsWare told was right, however, I forgot to inject the AppComponennt.

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        // this line was missing
        ((BaseApplication) getApplication()).getAppComponent().inject(this); 
        super.onCreate(savedInstanceState);
}



回答4:


    In HomeViewModel class need to extend ViewModel class.


public class MovieListViewModel extends ViewModel {
        private MutableLiveData<MovieModel> data;
        private MovieRepository movieModel;

        public MovieListViewModel() {
            movieModel = new MovieRepository();
        }

        public void init() {
            if (this.data != null) {
                // ViewModel is created per Fragment so
                // we know the userId won't change
                return;
            }
            data = movieModel.getMovies();
        }

        public MutableLiveData<MovieModel> getMovies() {
            return this.data;
        }
    }

https://github.com/kamydeep00178/androidMvvm can aslo check full example




回答5:


Try adding this:

private LiveData<Section[]> movies;

And then, change this:

LiveData<List<Movie>> getMovies() {
    if(movies==null)
        movies= movieRepository.getMovies();
    return movies;
}

It worked for me.




回答6:


As it was mentioned in other answers, ViewModelProviders cannot inject objects that you require in ViewModel constructor.

If you need to set up ViewModel quick and ugly without any factories, then you can use Dagger2 that would create getYourViewModel() method in the generated DaggerActivityComponent, while putting those objects into your constructor that you made available with @Provides annotation. Then just add this field to your activity:

@Inject
lateinit var viewmodel: YourViewModel

and inject it in OnCreate() and that's it. NB: Be careful with this approach with heavy and slow ViewModels.




回答7:


Based on this answer, you can do this

class MvpApp : Application(){

    companion object {
        lateinit var application: Application
    }

    override fun onCreate() {
        super.onCreate()
        application = this
    }
}

and then:

class ProfileViewModel: AndroidViewModel(MvpApp.application) {}


来源:https://stackoverflow.com/questions/48605672/cannot-create-an-instance-of-viewmodel-class-unable-to-start-activity-component

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!