Search code examples
androidkotlinandroid-architecture-components

Android: Get a DAO in a ViewModel


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"

Solution

  • 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.