I am using MVVM, Retrofit, LiveData in my project but I get this error before that I saw these links
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
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();
}}
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();
} }
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?
Using ViewModelProvider is the right way to create ViewModel . When the activity or fragment is created, ViewModelProvider is smart enough to figure out to reuse the first created ViewModel instance. If ViewModel doesn't change (which is likely true), using val Kotlin variable is a better option here.
As the library is the one responsible for creating the ViewModel, we do not get to call its constructor, the library is doing that internally, and by default it will always call the empty constructor, making it impossible to pass data to it.
Yes, the activity can pass the intent. extras into the ViewModel, through the AbstractSavedStateViewModelFactory constructor.
AndroidViewModel. Application context aware ViewModel . ViewModel is a class that is responsible for preparing and managing the data for an Activity or a Fragment . It also handles the communication of the Activity / Fragment with the rest of the application (e.g. calling the business logic classes).
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()
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 {
ViewModelProvider(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<com.gaston.example.viewmodel.MainViewModel> 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 ViewModelProvider
ViewModelProvider(this).get(MainViewModel(ItemListUseCase()))
But if we do this, we will be getting the same error too.
What we need to understand is that
ViewModelProvider(this).get(MainViewModel::class.java)
ViewModelProvider()
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 ViewModelProvier()
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 ViewModelProvier()
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 {
ViewModelProvider(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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With