База данных комнат: получение SELECT MAX () дважды после создания нового элемента RecyclerView - PullRequest
0 голосов
/ 10 ноября 2019

У меня есть список элементов CardView RecyclerView, который работает правильно. После создания нового CardView, который вставляется в базу данных, я хотел бы запустить тост, который информирует пользователя об успешном добавлении CardView и показывает номер CardView. Номер CardView - это идентификатор элемента CardView, вставленного в базу данных. Данные сохраняются в базе данных, когда пользователь нажимает кнопку «Сохранить», которая запускает onClickSave ().

Я установил @Query в Dao, чтобы получить MAX (cardId):

Dao
...
@Query("SELECT MAX(cardId) FROM cards")
LiveData<Integer> getMax();

@Insert
void insertCard(Card card);

Проблема в том, что два тоста стреляют. Первый тост возвращает ранее созданный номер CardView и , затем второй тост запускает и показывает последний добавленный номер CardView. Например, тост покажет CardView номер 33, а затем будет запущен второй тост, который показывает ожидаемый номер 34 CardView, который был только что создан (я подтверждаю, что CardViews 33 и 34 находятся как в базе данных, так и в двух самых высоких элементах, используя браузер БД дляПрограммное обеспечение 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 просто не вернет этот новый номер CardView, а не два тоста?

Для справки я показываю предыдущий код, который я использовал до Room и ViewModel, которые использовали курсор для получения самого последнего и самого высокого вставленного идентификатора:

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;
}      

Ответы [ 3 ]

1 голос
/ 13 ноября 2019

Операция Room Insert внутри AsyncTask занимает некоторое время, прежде чем переменная 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();
    });

На этом этапе код должен работать, ноВы получите несколько LiveData событий для одного Insert. Это происходит потому, что вы использовали 2 отдельных экземпляра Dao для операций Insert и Query.

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));
    }
1 голос
/ 14 ноября 2019

Операции модели представления, которые вы вызываете в 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);
}
0 голосов
/ 13 ноября 2019

Из-за использования AsyncTask для вставки карты в базу данных, выполнение этой функции займет некоторое время, и вы сразу же покажете свой тост! Измените свою активность на это:

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();
}
...