I had to create an account to ask this question because I couldn't find the right way to do this. The only thing that comes close is this question here, but it doesn't go all the way and I'm still stuck. Here we go...
I'm trying to build an app following as much of the Architecture Components principles.
I'm currently trying to add a row in one of my database table, and get the ID of this row in return, to then insert a row in another table, with a reference to the first one.
I've created my database object:
#Entity(indices = {#Index("id")})
public class Search {
#PrimaryKey(autoGenerate = true) private int id;
...
And the corresponding DAO:
#Dao
public interface SearchDao {
#Insert
long insert(Search search);
...
As you can see, my DAO returns a long with the created ID. This is the behavior which was pointed out in the question I linked before, and documented here.
Since I'm following Android Architecture Components principles, I'm using a Repository class to do all my database related work. In this Repository, I've created a public method to insert a new object, which is creating and executing an AsyncTask to do the work:
public class Repository {
public void insertSearch(Search search) {
new insertSearchAsyncTask(this.mSearchDao).execute(search);
}
...
private static class insertSearchAsyncTask extends AsyncTask<Search, Void, Long> {
private SearchDao mAsyncTaskDao;
insertSearchAsyncTask(SearchDao dao) {
this.mAsyncTaskDao = dao;
}
#Override
protected Long doInBackground(final Search... params) {
long id = this.mAsyncTaskDao.insert(params[0]);
return id;
}
}
I know I can use the onPostExecute(long id) method to do stuff with the result of the doInBackground method, but this onPostExecute method cannot return anything to the insertSearch method, where I created the AsyncTask and executed it.
I know need to change the return type of my insertSearch method to long. However if I want to have something to return, I need to get the result of the execution of the AsyncTask. How can I do that?
I've tried this (according to the validated answer):
public class Repository {
private long result_id = 0;
public long insertSearch(Search search) {
new insertSearchAsyncTask(this.mSearchDao).execute(search);
return result_id;
}
private class insertSearchAsyncTask extends AsyncTask<Search, Void, Long> {
private SearchDao mAsyncTaskDao;
insertSearchAsyncTask(SearchDao dao) {
this.mAsyncTaskDao = dao;
}
#Override
protected Long doInBackground(final Search... params) {
long id = this.mAsyncTaskDao.insert(params[0]);
return id;
}
#Override
protected void onPostExecute(Long search_id) {
result_id = search_id;
}
}
}
But this feels very very wrong. I had to make the insertSearchAsyncTask class not-static, and I have to store the result of the insert in an attribute of my Repository.
I'm hoping there is a better/correct way of doing this.
I've also looked at other suggested answers on the link above, especially one about Delegates, but this doesn't suit my need as I need the method insertSearch to return the result, not another one called by the AsyncTask when it finishes.
I hope I've explained my problem clearly enough.
Any idea anyone?
Thanks a lot!!
Related
I have entity Article I'm trying to retrieve one random record from database collection.
This is entity Article:
#Data
#Document(value = "article")
public class Article {
#Id
private String articleId;
private String title;
private String description;
private String fullArticle;
This is service to save it:
#Override
public Article save(Article article) {
return articleRepository.save(article);
}
And repository:
#Repository
public interface ArticleRepository extends MongoRepository<Article, String> {
}
So, now I'm trying to create a method that will get me one random record from my collection Article also, I want to create a controller so when I go to some endpoint and submit some get method to retrieve one record from the collection and so I can check it in postman or with Swagger. I find some answers to similar question to mine but no one solved my problem, I want to have API for something like that.
You can use $sample in an aggregation query to get a random document:
db.collection.aggregate([
{
"$sample": {
"size": 1
}
}
])
Example here
I've tested the code and it works as expected.
You can create add this method into repository:
#Aggregation(pipeline={"{$sample:{size:1}}"})
AggregationResults<Article> random();
And call from service like this:
#Override
public Article random(){
return articleRepository.random().getMappedResults().stream().findFirst().orElse(null);
// also you can use .orElseThrow() or whatever you want
}
This question is somehow related to my last question, because it is the same project but now I am trying to go one more step forward.
So, in my previous question I only had one table; this time I have two tables: the new second table is supposed to contain related attributes for the rows of the first table, in a OneToMany relationship. So, I store a ForeignKey in the second table that would store the Row ID of the first table's related row (obviously).
The problem is this: the intention is creating both registers (parent and child) at the same time, using the same form, and ParentTable uses AUTO_INCREMENT for his PrimaryKey (AKA ID).
Due to how RoomDb works, I do the creation using a POJO: but after insertion, this POJO won't give me the auto-generated ID as far as I know... so, the only workaround I am able to imagine is, when submitting the form, first make the INSERT for the parent, then using one of the form's fields that created the parent to make some kind of "SELECT * FROM parent_table WHERE field1 LIKE :field1", retrieving the ID, and then use that ID to create the child table's POJO and perform the next INSERT operation. However I feel something's not right about this approach, the last time I implemented something similar this way I ended up with a lot of Custom Listeners and a callback hell (I still have nightmares about that).
About the Custom Listeners thing, it is the solution I ended up choosing for a different problem for a different project (more details about it in this old question). Taking a look to that old question might help adding some context about how misguided I am in MVVM's architecture. However, please notice the current question has nothing to do with WebServices, because the Database is purely local in the phone's app, no external sources.
However, I am wondering: isn't this overkill (I mean the INSERT parent -> SELECT parentID -> INSERT child thing)? Is it inevitable having to do it this way, or there is rather a more clean way to do so?
The "create method" in my Repository class looks like this:
public void insertParent(Parent parent) {
new InsertParentAsyncTask(parent_dao).execute(parent);
}
private static class InsertParentAsyncTask extends AsyncTask<Parent, Void, Void> {
private final ParentDao parent_dao;
private InsertParentAsyncTask(ParentDao parent_dao) {
this.parent_dao = parent_dao;
}
#Override
protected Void doInBackground(Parent... parents) {
parent_dao.insert(parents[0]);
return null;
}
}
Trying to follow Mario's answer, I changed this method in my parent's DAO:
// OLD
#Insert
void insert(Parent parent);
// NEW (yes, I use short type for parent's ID)
#Insert
short insert(Parent parent);
EDIT2:
Now, I am trying to make changes to my Repository's insert AsyncTask, like this:
private static class InsertParentAsyncTask extends AsyncTask<Parent, Void, Short> {
private final ParentDao parent_dao;
private InsertParentAsyncTask(ParentDao parent_dao) {
this.parent_dao = parent_dao;
}
#Override
protected Short doInBackground(Parent... parents) {
short parent_id;
parent_id = parent_dao.insert(parents[0]);
return parent_id;
}
#Override
protected void onPostExecute(Short hanzi_id) {
// TODO ??? What now?
}
}
LONG STORY SHORT
It worked for me this way down here, but this ain't clean code (obviously):
// TODO I am aware that AsyncTask is deprecated
// My Repository class uses this
public void insertParentAndChildren(Parent parent, String[] children_list) {
new InsertParentAndChildrenAsyncTask(parent_dao, children_list).execute(parent);
}
private static class InsertParentAndChildrenAsyncTask extends AsyncTask<Parent, Void, Short> {
private final ParentDao parent_dao;
private String[] children_list;
private InsertParentAndChildrenAsyncTask(ParentDao parent_dao, String[] children_list) {
this.parent_dao = parent_dao;
this.children_list = children_list;
}
#Override
protected Short doInBackground(Parent... parents) {
short parent_id;
Long row_id = parent_dao.insert(parents[0]);
parent_id = parent_dao.getIdForRowId(row_id);
return parent_id;
}
#Override
protected void onPostExecute(Short parent_id) {
// Second "create method" for children
for (int n = 0; n < children_list.length; n++) {
Child child = new Child();
child.setParentId( parent_id );
child.setMeaning( children_list[n] );
// My Repository has this method as well
insertChildStaticMethod(child);
}
}
}
You are on the right track. A clean way would be to wrap it in a function like this:
fun saveParent(parent: Parent): Int {
val rowId = parentDao.insert(parent) // Returns Long rowId
val parentId = parentDao.getIdForRowId(rowId) // SELECT id FROM table_parent WHERE rowid = :rowId
return parentId
}
This function could be part of a repository class to make it even more clean.
Your functions in DAO can return the rowId and Parent.ID like this:
#Insert
fun insert(parent: Parent): Long
#Query("SELECT ID FROM table_parent WHERE rowid = :rowId")
fun getIdForRowId(rowId: Long): short
If you want to get basic functionality working first, you can call the Room database functions on the main thread when you build your database with allowMainThreadQueries():
MyApp.database = Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").allowMainThreadQueries().build()
Like this, you can postpone background processing to later. If you have specific questions about that subject, it is better to ask a separate question.
I think you could try SELECT last_insert_rowid() as a query on room (you write it just like that no need to reference any table). This statement returns the rowid of the last insertion into your database. By default rowId is what most sql DBs use as primary keys when you define them as auto incremental integers. so I guess you would define the following method in your DAO
#Query("SELECT last_insert_rowid()")
public abstract int getLastId()
#Insert
void insert(Parent parent)
then you can use it together with your insert statement in a transaction. like so
#Transaction
public int insertAndGetPrimaryKey(Parent parent){
insert(parent);
return getLastId();
}
it is important to use transaction as else the id delivered could be the wrong one if in your app multiple threads could potentially modify the tables at the same time.
btw I would not use short for a primary key. not only is it short XD (only 32k capacity) but the satndard is really to use Integer (4 bn capacity). if these were the 80's id be all for it (those are 2 entire bytes that you are saving after all XD) but now a days memory is cheap and abundant and as i said integer is what DBs work with by default.
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();
}
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.
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.
My issue is how to organize the code. Let say I have a User class
public class User extends RealmObject {
#PrimaryKey
private String id;
#Required
private String name;
public User() { // per requirement of no args constructor
id = UUID.randomUUID().toString();
}
// Assume getter & setter below...
}
and a Util class is needed to handles the save in an asynchronous manner since RealmObjects cannot have methods other than getter/setter.
public class Util {
public static void save(User user, Realm realm) {
RealmAsyncTask transaction = realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealm(user); // <====== Argument needs to be declared final in parent method's argument!
}
}, null);
}
}
The intention is to put save() in a Util class to prevent spreading similar save code all over the code-base so that every time I wanted to save I would just call it as such:
User u = new User();
u.setName("Uncle Sam");
Util.save(u, Realm.getDefaultInstance());
Not sure if this affects performance at all, but I was just going to save all fields overwriting what was there except for the unique id field every single time.
The problem is that I now need to set the "user" argument as final in the Util.save() method, which means I cannot pass in the object I need to save other than once.
Is there a different way of handling this? Maybe a different pattern? Or am I looking at this all wrong and should go back to SQLite?
Why is it a problem to set public static void save(final User user, Realm realm) ? It just means you cannot reassign the user variable to something else.
That said, the existence of a save() method can be a potential code smell as you then spread the update behaviour across the code base. I would suggest looking into something like the Repository pattern (http://martinfowler.com/eaaCatalog/repository.html) instead.
Realm is actually working on an example showing how you can combine the Model-View-Presenter architecture with a Repository to encapsulate updates which is a good pattern for what you are trying to do here. You can see the code for it here: https://github.com/realm/realm-java/pull/1960
I have the method getPlan and I try to modify an object (attribute in my class)
class XYZ
/**
* Plan.
*/
private Plan plan;
#Override
public final Plan getPlan() {
subjects.addAll(plan.getSubjects());
...
return new Plan("1", subjects.size(), subjects);
}
#Override
public final Graph createGraph() {
Plan fPlan = getPlan();
...
return graph;
}
In the second method createGraph, I try to get the modified object (fPlan), but it's in the initial state (Plan plan). I hope you understand the situation.
Thanks in advance!
Either your method should not return a Plan, because Plan plan is an instance variable already. Plus you don't instantiate Plan plan anyway, so I guess you have to pick one of below pieces of code:
First piece: we make Plan plan an instance variable, and edit, modify and use that and only that. That means in this class, we don't have to return an Plans, as we have access to Plan plan in this instance of xyz.
class XYZ
private Plan plan;
#Override
public final void instantiatePlan() {
subjects.addAll(plan.getSubjects());
...
plan = new Plan("1", subjects.size(), subjects);
}
#Override
public final Graph createGraph() {
plan = instantiatePlan();
...
return graph;
}
second option: we remove the instance variable Plan plan, and in createGraph(), we call getPlan() with returns a Plan which we can modify and edit there. According to what I guess your context is, I'd go for the first option though.
class XYZ
#Override
public final Plan getPlan() {
subjects.addAll(plan.getSubjects());
...
return new Plan("1", subjects.size(), subjects);
}
#Override
public final Graph createGraph() {
Plan fPlan = getPlan();
...
//edit fPlan here.
return graph;
}
EDIT: Seeing your comment, I understand even less of your problem. First of all, in getPlan() you call getSubjects() on Plan plan, but it's not even instantiated, so it'll throw an NPE immediately. Second: let a method do what the name tells you: thus, getPlan() should just be return plan;, no more, no less, no editing, modifying in that method. I'd suggest making a constructor of class XYZ with params Plan plan:
public XYZ(Plan mPlan) {
this.plan = mPlan;
}
Or initializing it in the constructor:
public XYZ(ArrayList<Subject> subjects) {
this.plan = new Plan("1", subjects.size(), subjects);
}
Please tell where you want plan to be what.