0

I want to get the id auto-generated while performing insert operation on Room database. I am implementing MVVM (Model-View-ViewModel) architecture which makes use of DAO to fire queries to Room database. I have added a repository layer between viewmodel and DAO to create an AsyncTask to perform database operations. How do I get the output of insert operation (which is the inserted row's auto-generated id) to the fragment that uses the viewmodel. The layers are as follows: Fragment -> ViewModel -> Repository -> DAO

ListFragment.java

public class ListFragment extends Fragment {
    private ReminderViewModel viewModel;
    private int id;
    ...
        viewModel = ViewModelProviders.of(this).get(ReminderViewModel.class);
    ...
        id = viewModel.insert(new TodoReminder(0, description, date, time));
    ...
}

ReminderViewModel.java

public class ReminderViewModel extends AndroidViewModel {
    private ReminderRepository repository;

    public ReminderViewModel(@NonNull Application application) {
        super(application);
        repository = new ReminderRepository(application);
    }

    public int insert(TodoReminder reminder) {
        repository.insert(reminder);
    }
}

ReminderRepository.java

public class ReminderRepository {
    private ReminderDAO reminderDAO;

    public ReminderRepository(Application application) {
        AppDatabase db = AppDatabase.getDatabase(application);
        reminderDAO = db.getReminderDAO();
    }

    public int insert(TodoReminder reminder) {
        new insertAsyncTask(reminderDAO).execute(reminder);
    }

    private static class InsertAsyncTask extends AsyncTask<TodoReminder, Void, Integer> {
        private ReminderDAO asyncTaskDAO;

        insertAsyncTask(ReminderDAO dao) {
            asyncTaskDAO = dao;
        }

        @Override
        protected Integer doInBackground(final TodoReminder... reminders) {
            return asyncTaskDAO.insert(reminders[0]);
        }
    }
}

ReminderDAO.java

@Dao
public interface ReminderDAO {
    @Insert
    public int insert(TodoReminder... reminders);
}

ToDoReminder.java

public class TodoReminder implements Serializable {
    @PrimaryKey(autoGenerate = true)
    @NonNull
    private int id;
    ...
}

How should I get the int returned from the insert method of ReminderDAO and return it from the insert method in ReminderRepository?

Rahul
  • 143
  • 1
  • 17

2 Answers2

0

You can create your database table in such a way that the id is incremented automatically. In MySQL that is done via the auto_increment keyword. In SQL Server it is done via the identity(1, 1) syntax. In Access it is the autoincrement keyword. In Oracle and PostgreSQL it is done using sequences. If you manage to do this, then you will not need to manually work on incrementing these values. If, for some reason this is out of the question, then you can create a before insert trigger which will load the maximum id and add 1 to it, storing the result in the id. Or, if even that is out of the question, then you can load the last ID, but that has a different syntax in different databases. Or, you can run a query like this:

select max(id) + 1 from yourtable;

but beware possible performance issues and concurrency problems.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
  • I appreciate your suggestions. But what I need is simply the id returned by the insert method of room database. I just need that id passed through the layers from back-end to front-end. Have you any solution for that? – Rahul Jul 09 '19 at 10:49
0

I have the exact problem. This is what I did.

I created an interface

public interface Task
 {
    void processInsert(long id) 
}

supply the interface to the insert fcn in the repository

public class Repository {

private Dao myDao;


    public void insertMyObject(MyOBject object,Task myInterface ) {

         new insertAysncTask(myDao,myInterface).execute(object);
      }

    private static class insertAsyncTask extends AysncTask<MyObject,Void,Long>{
        private Dao mDao;
        private Task mTask;

        public insertAysncTask(Dao dao, Task task) {

            this.mDao = dao;
            this.mTask=task;
        }


        @Override
        protected Long doInBackground(MyObject... myObjects) {
            return mDao.insertMyObject(myObjects[0]);

        }

        @Override
        protected void onPostExecute(Long aLong) {
            super.onPostExecute(aLong);
            mTask.processInsert(aLong);
        }
    }


}

in the DAO class fcn should have return type of Long

public interface MyDao {
    @Insert
    Long insertMyObject(MyObject object);

have the ViewModel implement the interface

 public class MyObjectViewModel extends AndroidViewModel implements Task{

    private Repository mRepository;

    public MyObjectViewModel(@NonNull Application application) {
        super(application);
        mRepository = new Repository(application);
    }

    @Override
    public void processInsert(Long id) {

      // code for processing the id returned from the db insert

    }

    public void insertMyObject(MyObject object){

        mRepository.insertMyObject(object,this);}


}

in the activity/fragment call the ViewModel insert

mViewModel.insertMyObject(object);

Update

If you want to return the id to the activity or fragment then have the fragment/activity implement the interface instead of the viewmodel

ListFragment extends Fragment implements Task{
.....

@Override
public void processInsert(Long id){
   //process id here
  }

//call the insert fcn passing in the reference to the fragment

mViewModel.insertMyObject(object,this)
}

Modify the insert fcn in the View Model to accept a reference to the interface

public void insertMyObject(MyObject object, Task myInterface){

        mRepository.insertMyObject(object,myInterface);
}

with this approach, the asynctask in the repository insert fcn will hold a reference to the activity/fragment and if the activity/fragment is destroyed before the asynctask finishes this will be a problem. I think it's better to do the processing in the viewmodel if possible. Fragments/activities should only deal with UI concerns not data processing.

Alternative Method An alternative would be to use a LiveData with an observer.

public class Repository{

private Dao myDao;
private MutableLiveData<Long> dbInsertId = new MutableLiveData<>();

public void insertMyObject(MyObject object){

    insertAysnc(object)
  }

private void insertAysnc(final MyObject object){

   new Thread(new Runnable() {
            @Override
            public void run() {
                Long id=myDao.insertMyObject(object); //call Dao insert fcn
              dbInsertId.postValue(id); //set the value of the livedata to the valued returned from the DB insert fcn
            }
        }).start();
  }

public LiveData<Long> getDbInsertedId(){
        return dbInsertId;
    }

}

In the ViewModel define this fcn

public LiveData<Long> getDbInsertedId(){
        return mRepository.getDbInsertedId();// call the repository getId fcn
    }

in the onCreate of Activity/Fragment setup an observer on the LiveData

mViewModel= ViewModelProviders.of(this).get(MyViewModel.class);
mViewModel.getDbInsertedId().observe(this, new Observer<Long>() {
            @Override
            public void onChanged(Long aLong) {
               // do something with the long value returned from the database
            }
        });

mem100
  • 43
  • 1
  • 7
  • How will I get the inserted id in the fragment? something like 'int id = mViewModel.insertMyObject(object);' – Rahul Aug 17 '19 at 16:37
  • also I'm getting this error: 'Not sure how to handle insert method's return type. long insert(TodoReminder... reminders);' in dao class. Please help – Rahul Aug 17 '19 at 18:20
  • The return type should be Long (if inserting single object) or Long [ ] (if inserting multiple objects). Note it's case sensitive Long not long. – mem100 Aug 18 '19 at 03:52