1

In my code below I'm getting rowId. I've read that it's also possible to get the last inserted row id from @Insert directly. In my code I changed void insert to long and tried many other things as in examples I found on the internet, but every time I get errors. Would you like to provide me a code/solution to get the row/user ID from @Insert?

@Dao
public interface UserDao {

    @Insert
    void insert(UserEntity userEntity);

    @Update
    void update(UserEntity userEntity);

    @Delete
    void delete(UserEntity userEntity);

    @Query("DELETE FROM user_table")
    void deleteAllUsers();

    @Query("SELECT * FROM user_table")
    LiveData<List<UserEntity>> getAllUsers();

//    ====== from here ======

    @Query("SELECT * FROM user_table")
    LiveData<UserEntity> getRowId();

//    ====== till here ======

}

Entity

@Entity(tableName = "user_table")
public class UserEntity {

    @PrimaryKey(autoGenerate = true)
    private int id;

    private String userName;

    private String userTelephoneNumber;

    public UserEntity(String userName, String userTelephoneNumber) {
        this.userName = userName;
        this.userTelephoneNumber = userTelephoneNumber;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public String getUserName() {
        return userName;
    }

    public String getUserTelephoneNumber() {
        return userTelephoneNumber;
    }
}

Repository

public class UserRepository {
    private UserDao userDao;
    private LiveData<List<UserEntity>> allUsers;

    public UserRepository(Application application) {
        HandymanDatabase handymanDatabase = HandymanDatabase.getInstance(application);
        userDao = handymanDatabase.userDao();
        allUsers = userDao.getAllUsers();
    }

    public void insert(UserEntity userEntity) {
        new InsertUserAsyncTask(userDao).execute(userEntity);
    }

    public void update(UserEntity userEntity) {
        new UpdateUserAsyncTask(userDao).execute(userEntity);
    }

    public void delete(UserEntity userEntity) {
        new DeleteUserAsyncTask(userDao).execute(userEntity);
    }

    public void deleteAllUsers() {
        new DeleteAllUsersAsyncTask(userDao).execute();
    }

    public LiveData<List<UserEntity>> getAllUsers() {
        return allUsers;
    }

//    ====== from here ======

    public LiveData<UserEntity> getRowId() {
        return userDao.getRowId();
    }

//    ====== till here ======


    private static class InsertUserAsyncTask extends AsyncTask<UserEntity, Void, Void> {
        private UserDao userDao;

        private InsertUserAsyncTask(UserDao userDao) {
            this.userDao = userDao;
        }

        @Override
        protected Void doInBackground(UserEntity... userEntities) {
            userDao.insert(userEntities[0]);
            return null;
        }
    }

    private static class UpdateUserAsyncTask extends AsyncTask<UserEntity, Void, Void> {
        private UserDao userDao;

        private UpdateUserAsyncTask(UserDao userDao) {
            this.userDao = userDao;
        }

        @Override
        protected Void doInBackground(UserEntity... userEntities) {
            userDao.update(userEntities[0]);
            return null;
        }
    }

    private static class DeleteUserAsyncTask extends AsyncTask<UserEntity, Void, Void> {
        private UserDao userDao;

        private DeleteUserAsyncTask(UserDao userDao) {
            this.userDao = userDao;
        }

        @Override
        protected Void doInBackground(UserEntity... userEntities) {
            userDao.delete(userEntities[0]);
            return null;
        }
    }

    private static class DeleteAllUsersAsyncTask extends AsyncTask<Void, Void, Void> {
        private UserDao userDao;

        private DeleteAllUsersAsyncTask(UserDao userDao) {
            this.userDao = userDao;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            userDao.deleteAllUsers();
            return null;
        }
    }
}

ViewModel

public UserViewModel(@NonNull Application application) {
        super(application);
        userRepository = new UserRepository(application);
        allUsers = userRepository.getAllUsers();
    }

    public void insert(UserEntity userEntity) {
        userRepository.insert(userEntity);
    }

    public void update(UserEntity userEntity) {
        userRepository.update(userEntity);
    }

    public void delete(UserEntity userEntity) {
        userRepository.delete(userEntity);
    }

    public void deleteAllUsers() {
        userRepository.deleteAllUsers();
    }

    public LiveData<List<UserEntity>> getAllUsers() {
        return allUsers;
    }

//    ====== from here ======

    public LiveData<UserEntity> getRowId() {
        return userRepository.getRowId();
    }

//    ====== till here ======

}

Fragment/Activity

public class UserFavoritesFragment extends Fragment {

    private static final String TAG = "UserFavoritesFragment";

    private UserViewModel userViewModel;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        View view = getLayoutInflater().inflate(R.layout.fragment_user_favorites, container, false);

        final TextView textViewUserName = view.findViewById(R.id.textViewUserName);
        TextView textViewUserPhone = view.findViewById(R.id.textViewUserPhone);

        userViewModel = new ViewModelProvider(this).get(UserViewModel.class);

//    ====== from here ======

        userViewModel.getRowId().observe(getViewLifecycleOwner(), new Observer<UserEntity>() {
            @Override
            public void onChanged(UserEntity userEntity) {

                long rowId = userEntity.getId();

                Log.d(TAG, "onChanged: " + rowId);

            }
        });

//    ====== till here ======

        return view;
    }
}
Zain
  • 37,492
  • 7
  • 60
  • 84
kvejonsio
  • 13
  • 3

1 Answers1

2

You can do that using a listener interface that has a callback that accepts a long value of the inserted row id in the database.

Listener Interface

public interface NewIdListener {
    void onInsert(long id);
}

Dao

@Dao
public interface UserDao {

    @Insert
    long insert(UserEntity userEntity); // return id of the inserted userEntity
    
}

Repository

public class UserRepository {
    private Executor mExecutor = Executors.newSingleThreadExecutor();
    private UserDao userDao;
    ...
    
    public void insertUserEntity(final UserEntity entity, final NewIdListener listener) {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                listener.onInsert(userDao.insert(entity));
            }
        });
}
    

ViewModel

public void insertUserEntity(UserEntity entity, NewIdListener listener) {
    userRepository.insertUserEntity(entity, listener);
}       

Activity

userViewModel.insertUserEntity(new UserEntity("User Name", "12345678"), new NewIdListener() {
    @Override
    public void onInsert(final long id) {
        requireActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(requireActivity(), "Id: " + id, Toast.LENGTH_SHORT).show();
            }
        });
    }
});

Note: For background thread, I've used Executor instead of AsyncTask as AsyncTask is deprecated now.

Zain
  • 37,492
  • 7
  • 60
  • 84
  • thanks for your reply. but I still have two errors. one in Repository - Variable 'listener' is accessed from within inner class, needs to be declared final, and second in Activity - Cannot resolve symbol 'entity'. Do you know what am I doing wrong? – kvejonsio Jul 23 '20 at 12:01
  • @kvejonsio thank you for the comment, for the first, I just updated the repository section in answer with `final` keyword as `final NewIdListener listener` .. for the second issue the `entitity` is just your UserEntity instance that you want to insert into the database – Zain Jul 23 '20 at 12:07
  • You just need to create new object of your `UserEntity` class, like `new Entity(...)` and pass it to my `userViewModel.insertUserEntity` as a first parameter .. I can't figure its constructor out as you didn't provide your `UserEntity` class – Zain Jul 23 '20 at 12:29
  • @kvejonsio I updated the answer in activity section with `new UserEntity("User Name", "12345678")` .. hope it works now – Zain Jul 23 '20 at 12:41
  • @Zain See a similar question here: https://stackoverflow.com/questions/64721856/android-room-database-insert-method-returns-row-id-starting-at-0. sergiy tikhonov in the comment section of his answer mentions that MainAcitivity should not be passed as a listener to the ViewModel because ViewModel shouldn't hold a reference to the Activity. Would appreciate your thoughts on this. I would prefer to use a listener over LiveData observer for a number of reasons but not sure now because of sergiy's comments. – AJW Mar 28 '21 at 20:34
  • @Zain One question on the listener interface as I'm trying to understand the set up you recommend. So the Dao is an interface between the database and the Repository...is the listener an interface between the Dao where the long id is returned and the Repository? – AJW Mar 28 '21 at 21:14
  • @AJW you shouldn't pass any context/life-cycle aware component as a listener to the ViewModel .. as it can cause memory leak; because the ViewModel lasts more than the activity lifecycle, so it can hold a context that doesn't exist for instance when configuration changes – Zain Mar 31 '21 at 11:18
  • @AJW If I understood well, Dao is a interface not a listener.. it's abstraction layer to access Room database ... Yes the listener is between the Dao and the Repository .. it waits and listen to the Dao until it returns the int value back from the Room database .. because that can take a while and it's in a worker thread, then you should use a listener – Zain Mar 31 '21 at 11:25
  • @Zain Got it, thanks. So how would I get the int value back to the Activity if I should not put the Activity context listener in the ViewModel? In your answer above isn't your listener being defined in the Activity and then you put a reference to it in the ViewModel? Or is the listener defined somewhere else? – AJW Mar 31 '21 at 13:37
  • 1
    @AJW I am using anonymous listener in the activity, not in the ViewModel, it's just passed as a method parameter to the ViewModel, but not saved on it; that listener will be destroyed and recreated whenever the context is destroyed and recreated and eventually re-passed to the ViewModel, so it's independent of the ViewModel. – Zain Mar 31 '21 at 14:12
  • 1
    @Zain Ok I understand. Cheers! – AJW Mar 31 '21 at 14:16
  • 1
    @Zain Added your code tonight and working as expected. Answer upvoted, thank you. One thought is that it may be worth mentioning above that the requireActivity() method you show is for use with a Fragment. I was using your onInsert() method in an Activity so had to use just "runOnUIThread(()..." rather than your requireActivity().runOnUIThread((). – AJW Apr 01 '21 at 02:24
  • 1
    @AJW totally right >> if you are going to change UI, you've to use `runOnUIThread()`, and Toast is a kind of changing UI, if you will just set some variables, no need to this.. anytime and feel free to connect back if you encounter issues so that we can learn from one another :) – Zain Apr 01 '21 at 02:37
  • @Zain Well done on changing UI and thank you for the offer to connect and learn from one another! – AJW Apr 01 '21 at 02:56