What is the best way to get a Android Room DAO in a ViewModel?
Based on the paging library example I wrote this ViewModel:
class MyViewModel(myDao: MyDao) : ViewModel() {
val data = myDao.get().create(
/* initial load position */ 0,
PagedList.Config.Builder()
.setPageSize(50)
.setPrefetchDistance(50)
.build())
}
I then try to get an instance
val viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
Trying to run this I get an exception:
java.lang.RuntimeException: Unable to start activity ComponentInfo{....MyActivity}: java.lang.RuntimeException: Cannot create an instance of class ...MyViewModel
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2817)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Caused by: java.lang.RuntimeException: Cannot create an instance of class ...MyViewModel
at android.arch.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:145)
at android.arch.lifecycle.ViewModelProviders$DefaultFactory.create(ViewModelProviders.java:158)
at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:128)
at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:96)
...
Caused by: java.lang.InstantiationException: java.lang.Class<...MyViewModel> has no zero argument constructor
From the paging library example it is not apparent how the view model gets a copy of the DAO, and apparently it fails. Question is if I have missed something, or is the example incomplete?
Googling the exception I find suggestions to use a ViewModelProvider.Factory, only the example did not use this. In the example code the view model looks like this:
class MyViewModel extends ViewModel {
public final LiveData<PagedList<User>> usersList;
public MyViewModel(UserDao userDao) {
usersList = userDao.usersByLastName().create(
/* initial load position */ 0,
new PagedList.Config.Builder()
.setPageSize(50)
.setPrefetchDistance(50)
.build());
}
}
And is retreived like this
MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
My dependencies
def roomVersion = "1.0.0"
implementation "android.arch.persistence.room:runtime:$roomVersion"
annotationProcessor "android.arch.persistence.room:compiler:$roomVersion"
kapt "android.arch.persistence.room:compiler:$roomVersion"
implementation "android.arch.paging:runtime:1.0.0-alpha3"
I found this paging example by google. Based on that I wrote this view model (see CheeseViewModel):
class MyViewModel(app: Application) : AndroidViewModel(app) {
val data = MyDatabase.get(app).dao.get().create(
/* initial load position */ 0,
PagedList.Config.Builder()
.setPageSize(50)
.setPrefetchDistance(50)
.build())
}
And I added this to my DB class (see CheeseDb):
companion object {
// Google example noted that this might not be the best
// solution and to use a dependency injection framework instead.
private var instance: MyDatabase? = null
@Synchronized
fun get(context: Context): MyDatabase {
return instance ?: Room.databaseBuilder(context.applicationContext,
MyDatabase::class.java, "myDB")
.build()
.also { instance = it }
}
}
So that answers the question on how to get a DAO instance in a view model. Regarding the other question, I guess that the paging library example was incomplete.
In your code, you have parameterized constructor in your ViewModel class for that you need to pass parameter while initializing ViewModel.
Below is the code in java
public class UserInfoViewModel extends AndroidViewModel {
LiveData<PagedList<UserInfo>> usersForList;
private PersonRepository personRepository;
public UserInfoViewModel(@NonNull Application application) {
super(application);
personRepository = new PersonRepository(application);
}
public static class Factory extends ViewModelProvider.NewInstanceFactory {
@NonNull
private final Application mApplication;
public Factory(@NonNull Application application) {
mApplication = application;
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
//noinspection unchecked
return (T) new UserInfoViewModel(mApplication);
}
}
public void init() {
usersForList = personRepository.getAllPersonsForList().create(0,
new PagedList.Config.Builder()
.setEnablePlaceholders(true)
.setPageSize(50)
.setPrefetchDistance(10)
.build());
}
}
In your Activity, you can access the ViewModel as shown in below code
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
UserInfoViewModel.Factory factory = new UserInfoViewModel.Factory(
this.getApplication());
userInfoViewModel = ViewModelProviders.of(this,factory)
.get(UserInfoViewModel.class);
userInfoViewModel.init();
}
Here is my repository object in repository using DAO object as shown in below code
PersonRepository.java
public class PersonRepository {
private final ChatListDAO personDAO;
public PersonRepository(Context context) {
personDAO = DatabaseCreator.getChatDatabase(context).ChatDatabase();
}
}
DAO.java class
@Dao
public interface DAO{
@Insert(onConflict = OnConflictStrategy.REPLACE)
long insertPerson(UserInfo person);
@Update
void updatePerson(UserInfo person);
@Delete
void deletePerson(UserInfo person);
@Query("SELECT * FROM person")
LiveData<List<UserInfo>> getAllPersons();
@Query("SELECT count(*) FROM person")
LiveData<Integer> getAllPersonsCount();
@Query("SELECT * FROM person where number = :mobileIn")
LiveData<UserInfo> getPersonByMobile(String mobileIn);
@Query("SELECT * FROM person")
public abstract LivePagedListProvider<Integer,UserInfo> getUsersForList();
}
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