Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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"
like image 755
Love Avatar asked Nov 18 '17 14:11

Love


2 Answers

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.

like image 199
Love Avatar answered Sep 19 '22 07:09

Love


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();
}
like image 33
Prashant Kawtikwar Avatar answered Sep 19 '22 07:09

Prashant Kawtikwar