房间数据库:在创建新的 RecyclerView 项目后获得 SELECT MAX() 两次
Room database: getting SELECT MAX() twice after creating new RecyclerView item
我有一个正常工作的 CardView 项目的 RecyclerView 列表。创建插入数据库的新 CardView 后,我想触发一个 Toast,通知用户已成功添加 CardView 并显示 CardView 编号。 CardView 编号是插入数据库的 CardView 项的 Id。当用户单击触发 onClickSave() 的保存按钮时,数据将保存到数据库中。
我在 Dao 中设置了一个 @Query 来获取 MAX(cardId):
Dao
...
@Query("SELECT MAX(cardId) FROM cards")
LiveData<Integer> getMax();
@Insert
void insertCard(Card card);
问题是两个 Toast 正在触发。第一个 Toast 是 returning 先前创建的 CardView 编号 和 然后第二个 Toast 正在触发,它显示刚刚添加的最新 CardView 编号。例如,Toast 将显示 CardView 编号 33,然后触发第二个 Toast,显示刚刚创建的预期 CardView 编号 34(我确认 CardView 33 和 34 都在数据库中,并且是两个最高的项目,使用 DB Browser SQLite 软件)。
AddorUpdateCardActivity
...
private int newMax = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewModel = new ViewModelProvider(this).get(cardViewModel.class);
}
public void onClickSave(View v) {
// set card data
// then insert data in database
mViewModel.insertCard(card1);
mViewModel.getMax().observe(this, value -> { newMax = value; Toast.makeText(AddorUpdateCardActivity.this, "card #" + newMax + " was saved to the list", Toast.LENGTH_LONG).show();});
}
ViewModel
...
public cardViewModel(Application application) {
super(application);
repository = new cardRepository(application);
getMax = repository.getMax();
}
public LiveData<Integer> getMax() {
return getMax;
}
public void insertCard(Card card) {
repository.insertCard(card);
}
cardRepository
private CardDao cardDao;
private LiveData<Integer> getMax;
public cardRepository(Application application) {
RoomDatabase db = RoomDatabase.getDatabase(application);
cardDao = db.cardDao();
}
public LiveData<Integer> getMax() {
return cardDao.getMax;
}
public void insertCard(Quickcard newcard) {
AsyncTask.execute(() -> cardDao.insertCard(newcard));
}
我在这里错过了什么?如果卡片已正确插入数据库,那么为什么 ViewModel 观察者不只是 return 这个新的 CardView 编号而不是两个 Toasts?
作为参考,我展示了我在 Room 和 ViewModel 之前使用的代码,它使用游标获取最新和最高的插入 ID:
public class SQLiteDB extends SQLiteOpenHelper {
...
public int getLastInsertId() {
int index = 0;
SQLiteDatabase sdb = getReadableDatabase();
Cursor cursor = sdb.query(
"sqlite_sequence",
new String[]{"seq"},
"name = ?",
new String[]{TABLE_NAME},
null,
null,
null,
null
);
sdb.beginTransaction();
try {
if (cursor !=null) {
if (cursor.moveToLast()) {
index = cursor.getInt(cursor.getColumnIndex("seq"));
}
}
...
}
return index;
}
因为使用 AsyncTask 将卡插入数据库,该功能需要一些时间才能完成,您立即祝酒!将您的 activity 更改为:
AddorUpdateCardActivity
...
private int newMax = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewModel = new ViewModelProvider(this).get(cardViewModel.class);
mViewModel.getMax().observe(this, integer2 -> {
newMax = integer2;
Toast.makeText(AddorUpdateCardActivity.this, "card #" + newMax + " was saved to the list", Toast.LENGTH_LONG).show();
hideProgressBar();
});
}
public void onClickSave(View v) {
//set card data
// then insert data in database
mViewModel.insertCard(card1);
showProgressBar();
}
AsyncTask
内的房间 Insert
操作需要一段时间才能更新 maxCount
变量。由于您在单击按钮时显示 Toast
,因此会立即显示消息,而不会收到来自 LiveData
的更新值。
将 Toast
消息移到 obverve()
方法中,使其仅在 LiveData
更改后触发。
mViewModel.getMax().observe(this, value -> {
newMax = value;
Toast.makeText(AddorUpdateCardActivity.this, "card #" + newMax + " was saved to the list", Toast.LENGTH_LONG).show();
});
此时,代码应该可以正常工作,但是对于单个 Insert
,您将获得多个 LiveData
事件。发生这种情况是因为您为 Insert
和 Query
操作使用了 2 个单独的 Dao 实例。
public cardRepository(Application application) {
RoomDatabase db = RoomDatabase.getDatabase(application);
cardDao = db.cardDao(); // <---------- Instance #1
getMax = cardDao.getMax();
}
public LiveData<Integer> getMax() {
return getMax;
}
public void insertCard(Card newcard) {
new InsertAsyncTask(quickcardDao).execute(newcard);
}
private static class InsertAsyncTask extends AsyncTask<Card, Void, Integer> {
private CardDao asyncTaskDao;
InsertAsyncTask(CardDao dao) {
asyncTaskDao = dao; // <---------- Instance #2
}
@Override
protected Integer doInBackground(final Card... params) {
asyncTaskDao.insertCard(params[0]);
return null;
}
}
要解决它,请在所有地方使用相同的 Dao 实例:
public cardRepository(Application application) {
RoomDatabase db = RoomDatabase.getDatabase(application);
cardDao = db.cardDao();
}
public LiveData<Integer> getMax() {
return cardDao.getMax();
}
public void insertCard(Card newcard) {
AsyncTask.execute(() -> cardDao.insertCard(newcard));
}
您在 onClickSave
中调用的视图模型操作是异步的:
public void onClickSave(View v) {
mViewModel.insertCard(card1);
mViewModel.getMax().observe(this, value -> { newMax = value; makeText(AddorUpdateCardActivity.this, "TEXT", .LENGTH_LONG).show();});
}
LiveData
的实现记录了数据版本以及观察者看到的最后一个版本。
因此 insertCard
开始在工作线程上运行,而您开始使用新创建的观察者从主线程观察 getMax
。因此,您将收到当前值以及数据库更新后的新值。
相反,您可以在 onCreate()
中只观察一次并等待数据库触发的更新:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewModel = new ViewModelProvider(this).get(cardViewModel.class);
mViewModel.getMax().observe(this, value -> { newMax = value; makeText(AddorUpdateCardActivity.this, "TEXT", .LENGTH_LONG).show();});
}
public void onClickSave(View v) {
mViewModel.insertCard(card1);
}
我有一个正常工作的 CardView 项目的 RecyclerView 列表。创建插入数据库的新 CardView 后,我想触发一个 Toast,通知用户已成功添加 CardView 并显示 CardView 编号。 CardView 编号是插入数据库的 CardView 项的 Id。当用户单击触发 onClickSave() 的保存按钮时,数据将保存到数据库中。
我在 Dao 中设置了一个 @Query 来获取 MAX(cardId):
Dao
...
@Query("SELECT MAX(cardId) FROM cards")
LiveData<Integer> getMax();
@Insert
void insertCard(Card card);
问题是两个 Toast 正在触发。第一个 Toast 是 returning 先前创建的 CardView 编号 和 然后第二个 Toast 正在触发,它显示刚刚添加的最新 CardView 编号。例如,Toast 将显示 CardView 编号 33,然后触发第二个 Toast,显示刚刚创建的预期 CardView 编号 34(我确认 CardView 33 和 34 都在数据库中,并且是两个最高的项目,使用 DB Browser SQLite 软件)。
AddorUpdateCardActivity
...
private int newMax = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewModel = new ViewModelProvider(this).get(cardViewModel.class);
}
public void onClickSave(View v) {
// set card data
// then insert data in database
mViewModel.insertCard(card1);
mViewModel.getMax().observe(this, value -> { newMax = value; Toast.makeText(AddorUpdateCardActivity.this, "card #" + newMax + " was saved to the list", Toast.LENGTH_LONG).show();});
}
ViewModel
...
public cardViewModel(Application application) {
super(application);
repository = new cardRepository(application);
getMax = repository.getMax();
}
public LiveData<Integer> getMax() {
return getMax;
}
public void insertCard(Card card) {
repository.insertCard(card);
}
cardRepository
private CardDao cardDao;
private LiveData<Integer> getMax;
public cardRepository(Application application) {
RoomDatabase db = RoomDatabase.getDatabase(application);
cardDao = db.cardDao();
}
public LiveData<Integer> getMax() {
return cardDao.getMax;
}
public void insertCard(Quickcard newcard) {
AsyncTask.execute(() -> cardDao.insertCard(newcard));
}
我在这里错过了什么?如果卡片已正确插入数据库,那么为什么 ViewModel 观察者不只是 return 这个新的 CardView 编号而不是两个 Toasts?
作为参考,我展示了我在 Room 和 ViewModel 之前使用的代码,它使用游标获取最新和最高的插入 ID:
public class SQLiteDB extends SQLiteOpenHelper {
...
public int getLastInsertId() {
int index = 0;
SQLiteDatabase sdb = getReadableDatabase();
Cursor cursor = sdb.query(
"sqlite_sequence",
new String[]{"seq"},
"name = ?",
new String[]{TABLE_NAME},
null,
null,
null,
null
);
sdb.beginTransaction();
try {
if (cursor !=null) {
if (cursor.moveToLast()) {
index = cursor.getInt(cursor.getColumnIndex("seq"));
}
}
...
}
return index;
}
因为使用 AsyncTask 将卡插入数据库,该功能需要一些时间才能完成,您立即祝酒!将您的 activity 更改为:
AddorUpdateCardActivity
...
private int newMax = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewModel = new ViewModelProvider(this).get(cardViewModel.class);
mViewModel.getMax().observe(this, integer2 -> {
newMax = integer2;
Toast.makeText(AddorUpdateCardActivity.this, "card #" + newMax + " was saved to the list", Toast.LENGTH_LONG).show();
hideProgressBar();
});
}
public void onClickSave(View v) {
//set card data
// then insert data in database
mViewModel.insertCard(card1);
showProgressBar();
}
AsyncTask
内的房间 Insert
操作需要一段时间才能更新 maxCount
变量。由于您在单击按钮时显示 Toast
,因此会立即显示消息,而不会收到来自 LiveData
的更新值。
将 Toast
消息移到 obverve()
方法中,使其仅在 LiveData
更改后触发。
mViewModel.getMax().observe(this, value -> {
newMax = value;
Toast.makeText(AddorUpdateCardActivity.this, "card #" + newMax + " was saved to the list", Toast.LENGTH_LONG).show();
});
此时,代码应该可以正常工作,但是对于单个 Insert
,您将获得多个 LiveData
事件。发生这种情况是因为您为 Insert
和 Query
操作使用了 2 个单独的 Dao 实例。
public cardRepository(Application application) {
RoomDatabase db = RoomDatabase.getDatabase(application);
cardDao = db.cardDao(); // <---------- Instance #1
getMax = cardDao.getMax();
}
public LiveData<Integer> getMax() {
return getMax;
}
public void insertCard(Card newcard) {
new InsertAsyncTask(quickcardDao).execute(newcard);
}
private static class InsertAsyncTask extends AsyncTask<Card, Void, Integer> {
private CardDao asyncTaskDao;
InsertAsyncTask(CardDao dao) {
asyncTaskDao = dao; // <---------- Instance #2
}
@Override
protected Integer doInBackground(final Card... params) {
asyncTaskDao.insertCard(params[0]);
return null;
}
}
要解决它,请在所有地方使用相同的 Dao 实例:
public cardRepository(Application application) {
RoomDatabase db = RoomDatabase.getDatabase(application);
cardDao = db.cardDao();
}
public LiveData<Integer> getMax() {
return cardDao.getMax();
}
public void insertCard(Card newcard) {
AsyncTask.execute(() -> cardDao.insertCard(newcard));
}
您在 onClickSave
中调用的视图模型操作是异步的:
public void onClickSave(View v) {
mViewModel.insertCard(card1);
mViewModel.getMax().observe(this, value -> { newMax = value; makeText(AddorUpdateCardActivity.this, "TEXT", .LENGTH_LONG).show();});
}
LiveData
的实现记录了数据版本以及观察者看到的最后一个版本。
因此 insertCard
开始在工作线程上运行,而您开始使用新创建的观察者从主线程观察 getMax
。因此,您将收到当前值以及数据库更新后的新值。
相反,您可以在 onCreate()
中只观察一次并等待数据库触发的更新:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewModel = new ViewModelProvider(this).get(cardViewModel.class);
mViewModel.getMax().observe(this, value -> { newMax = value; makeText(AddorUpdateCardActivity.this, "TEXT", .LENGTH_LONG).show();});
}
public void onClickSave(View v) {
mViewModel.insertCard(card1);
}