Mahozad
Mahozad

Reputation: 24582

How to get id of the inserted entity when using ViewModel?

The root problem: I want to set the id of [Entity A] in foreign key of [Entity B] but id of [Entity A] is not available until inserted in the database (because it is autogenerated by the DBMS).

Using architecture components (Room, ViewModel and LiveData), how can I perform a transaction that saves multiple related entities in the database? The following code currently resides in the ViewModel and works fine. The problem is I want to put this AsyncTask in the repository layer like other simple one-operation queries, but is it OK? Because in that case the repository would be responsible for managing relationships and knowing about entity details.

As I said above, the main problem is that I need id of the inserted entity so I can save it in another entity. If this requirement didn't exist, I would be able to persist each entity one by one in separate AsyncTasks in the repository.

MainViewModel.java:

public void buy(Item item, Store store) {

    new AsyncTask<Void, Void, Void>() {
        @Override
        protected Void doInBackground(Void... voids) {
            long storeId = mRepository.insertStore(store);

            Purchase purchase = new Purchase(storeId); // here uses id of the store
            long purchaseId = mRepository.insertPurchase(purchase);

            item.setPurchaseId(purchaseId); // here uses id of the purchase
            mRepository.updateItem(item);

            return null;
        }
    }.execute();
}

Upvotes: 3

Views: 1052

Answers (2)

Thibault Seisel
Thibault Seisel

Reputation: 1257

You can execute multiple database operations in a transaction using Android Room. This way, you are ensured that your database integrity is not altered in case one of those operation fails (operations are rolled-back).

Here is how you can define a Transaction with Room in the Dao class:

@Dao
public abstract class MyDao {

    @Insert
    public abstract long insertStore(Store store);

    @Insert(onConflict = OnConflictStrategy.ROLLBACK)
    public abstract long recordPurchase(Purchase purchase);

    @Update
    public abstract void updateItem(Item updatedItem);

    @Transaction
    public void buyItemFromStore(Item boughtItem, Store store) {
        // Anything inside this method runs in a single transaction.
        long storedId = insertStore(store);

        Purchase purchase = new Purchase(storeId);
        long purchaseId = recordPurchase(purchase);

        item.setPurchaseId(purchaseId);
        updateItem(item);
    }
}

You can refer to the documentation for an explanation on how @Transaction works.

Then in your repository class, call the buyItemFromStore from your AsyncTask:

public class MyRepository {
    private MyDao dao;

    public void buy(Item item, Store store) {
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doOnBackground(Void... voids) {
                // Everything is saved in a transaction.
                dao.buyItemFromStore(item, store);
                return null;
            }
        }
    }
}

Note that this is perfectly fine for the Repository layer to be aware of relationships between entities, as long as the stored objects are related in some way (with Store Purchase and Item it seems to be the case).

If you are unable to alter your Dao class, consider RoomDatabase.runInTransaction.

Upvotes: 0

crymson
crymson

Reputation: 1190

I think what you're doing is fine if you keep this in the Repository layer. I don't think keeping this in the ViewModel is a good idea as it's suppose to be the Repository's responsibility to handle your data, in this case, the Item and Store objects. I believe that your Repository should be responsible for the management of this data and its relationships. To answer your question about receiving the ID of the updated entity, what you can do is have your AsyncTask implement the onPostExecute method and have your doInBackground method return an actual value (like the storeId) instead of null. You can then have onPostExecute retrieve that value and delegate control to a callback listener of some sort.

Upvotes: 1

Related Questions