Android Room: обратный вызов LiveData для вставки обновления?

У меня есть Простой DAO с функцией CRUD

FeedEntryDAO.java

@Dao
public interface FeedEntryDAO {

  @Query("SELECT * FROM feedEntrys")
  LiveData<List<FeedEntry>> getAll();

  @Query("SELECT * FROM feedEntrys WHERE uid = :uid LIMIT 1")
  LiveData<FeedEntry> findByUid(int uid);

  @Insert
  void insertAll(FeedEntry... feedEntries);

  @Delete
  void delete(FeedEntry feedEntry);

  @Update
  int update(FeedEntry feedEntry);

}

Для select нормально вернуть тип LiveData.

Внутри Activity код хорош для выбора

viewModel.getFeedEntrys().observe(this,entries -> {...});

Однако когда я пытаюсь вставить, обновить, удалить данные. Код кажется немного некрасивым, а также каждый раз создает асинхронную задачу.

new AsyncTask<FeedEntry, Void, Void>() {
                @Override
                protected Void doInBackground(FeedEntry... feedEntries) {
                  viewModel.update(feedEntries[0]);
                  return null;
                }
}.execute(feedEntry);

У меня есть 2 вопроса по этому поводу:

  1. Могу ли я использовать LiveData для обертывания функций удаления, вставки и обновления?
  2. Лучший способ сохранить такой класс asynctask для удаления, вставки, обновления?

Ценю любые предложения и советы. Спасибо.


person Long Ranger    schedule 28.09.2017    source источник


Ответы (7)


  1. Могу ли я использовать LiveData для обертывания вызовов Delete, Insert, Update?

Нет, не можешь. Я написал ответ на проблему. Причина в том, что LiveData используется для уведомления об изменениях. Вставка, обновление, удаление не вызывают изменений. Он вернет удаленные строки, вставленный идентификатор или затронутые строки. Даже если это выглядит ужасно, имеет смысл не обертывать LiveData вокруг ваших материалов. В любом случае, имеет смысл иметь что-то вроде Single вокруг вызовов, чтобы позволить операции запускаться и работать с операцией RX-Java.

Если вы хотите инициировать эти вызовы, вы наблюдаете за запросом выбора, который уведомляет ваш LiveData onec, что вы обновили, вставили или удалили некоторые / любые данные.

  1. Лучший способ сохранить такой класс asynctask для удаления, вставки, обновления?

Посмотрев на ваш пример, похоже, что вы неправильно используете (Model / View /) ViewModel-Pattern. Вы никогда не должны обращаться к своему репозиторию в вашем представлении. Я не уверен, что вы делаете это, потому что это не видно в вашем образце. В любом случае, после наблюдения за вашими LiveData и получения результата нет необходимости оборачивать обновление данных внутри вашего viewModel в AsyncTask. Это означает, что вы всегда должны заботиться о

a) просматривать ‹-> viewmodel ‹-> репозиторий, а не просматривать ‹-> репозиторий и просматривать ‹-> viewmodel

а также

б) не пытайтесь использовать ненужные потоки. Вы наблюдаете LiveData в фоновом потоке (@WorkerThread) по умолчанию (если не аннотировано с помощью @MainThread) и получаете значение в пользовательском потоке (@MainThread).

person Emanuel S    schedule 29.09.2017
comment
Фактически, моя деятельность вызывает не репозиторий, а модель представления. Мой образец не показывает это четко. Это моя неудача. Лучше использовать Rxjava для решения этой проблемы. Спасибо за комментарий, я постараюсь изменить код и выложить здесь лучший. - person Long Ranger; 30.09.2017
comment
Хорошо, я последую за этим вопросом и изменю свой ответ, как только вы обновите свой код - person Emanuel S; 30.09.2017
comment
В любом случае ... Я думаю, что фреймворк должен предлагать более простые в использовании утилиты для планирования коротких заданий в фоновом режиме. Я имею в виду ... если вы не используете внешние библиотеки, такие как RxJava, вы вынуждены использовать AsyncTask или IntentService или что-то подобное ... но для правильного управления им требуется много шаблонного кода. - person andrea.rinaldi; 06.11.2017
comment
Вот как они ловят вас на котлине :-) - person Emanuel S; 06.11.2017
comment
@EmanuelS, я не понял, как сделать вставку внутри модели просмотра без asnyctask - person Xan; 24.06.2019
comment
@EmanuelS Как мы можем убедиться, что наблюдаем (извлекаем) данные только после завершения операции вставки? Я имею в виду, предположим, что если у меня есть 4 разных таблицы в базе данных, и все они вставляются в разные AsyncTask (ы), поэтому они будут делать это в 4 фоновых потоках, так как я могу создать механизм ожидания перед всеми операциями вставки завершены перед загрузкой? - person Aman Grover; 18.04.2020

Что касается второго вопроса, есть еще одна более удобная альтернатива AsyncTask; который использует java Executor, хорошая новость заключается в том, что вы можете использовать один экземпляр Executor вместо нескольких экземпляров AsyncTask для всех операций CRUD.

Демо-пример

public class Repository {

    private static Repository instance;
    private MyDatabase mDatabase;
    private Executor mExecutor = Executors.newSingleThreadExecutor();

    private Repository(Application application) {
        mDatabase = MyDatabase.getInstance(application.getApplicationContext());
    }

    public static Repository getInstance(Application application) {
        if (instance == null) {
            instance = new Repository(application);
        }
        return instance;
    }


    public void insert(final MyModel model) {

        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                mDatabase.getMyModelDAO().insert(model);
            }
        });
    }

    public void update(final MyModel model) {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                mDatabase.getMyModelDAO().update(model);
            }
        });
    }

    public void delete(final MyModel model) {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                mDatabase.getMyModelDAO().delete(model);
            }
        });
    }

}
person Zain    schedule 14.04.2019
comment
Хороший ответ 5 лет назад. Я использую базу данных Room с такими компонентами архитектуры Android, как LiveData и ViewModel. Это Executors.newSingleThreadExecutor (); по-прежнему лучший метод, чем AsyncTask? - person AJW; 09.05.2019
comment
Другой вопрос. Если я сделаю несколько вставок в своей MainActivity, не будет ли приведенный выше код создавать нового исполнителя для каждой вставки? Или одноэлементный экземпляр репозитория создает только одного исполнителя, который можно повторно использовать, если есть несколько вставок? - person AJW; 09.05.2019
comment
Привет @AJW для второго и хорошего вопроса, для всех вставок создается только один экземпляр исполнителя, что bcz причина, по которой вы упомянули как Repository, является singleton, переменные-члены класса Repository создаются только во время создания экземпляра класса (так же, как конструкторы), поэтому они вызываются только один раз. вы можете убедиться, что с помощью 'static' mExecutor ... - person Zain; 09.05.2019
comment
Что касается первого вопроса, вы правы. Исполнители устарели, но не устарели; но в официальном документе говорится, что AsyncTask подходит для коротких операций; что, если вы удаляете или вставляете сразу большой объем данных в базу данных, что может занять некоторое время ?, также AsyncTask, возможно, лучшее решение, которое я все еще использую при обновлении пользовательского интерфейса с помощью onPostExecute, но здесь с базой данных CRUD компоненты архитектуры делают это за вас , так что, может быть, исполнители больше подходят. - person Zain; 09.05.2019
comment
Когда вы используете LiveData, CRUD выполняется за вас за кулисами. Для обновлений, вставок и удалений вы должны что-то использовать. Так почему вы говорите, что Executors устарели? Есть ли какой-нибудь другой более новый метод, помимо AsyncTask, который лучше, чем Executors? - person AJW; 09.05.2019
comment
@AJW извините, если это неправильно поняли, я просто хотел проиллюстрировать вашу фразу Хороший ответ 5 лет назад, что я знаю, что Excutors старые, но не устаревают, когда AsyncTask появляется в жизни; Я имел в виду, что если AsyncTask новый, это не значит, что его можно использовать во всех случаях. - person Zain; 10.05.2019
comment
@Zain имеет ли это какое-либо преимущество перед простым выполнением: `` AsyncTask.execute (Runnable) `` Кажется - если я чего-то не упускаю - с этим статическим методом, и, следовательно, нет необходимости создавать экземпляр вашего собственного Executor, например было бы немного проще. - person Kevin Worth; 26.05.2019
comment
@KevinWorth в документации AsyncTask упоминается несколько вещей: во-первых, он живет всего несколько секунд; поэтому, если вы просто выполняете операцию, которая занимает максимум несколько секунд, то и AsyncTask, и Executors равны, но Executors типичны для длинных операций, таких как, например, удаление группы строк в вашей базе данных. - person Zain; 28.05.2019
comment
во-вторых, AsyncTask создается для публикации результатов в потоке пользовательского интерфейса, когда ваш фоновый поток завершен; и здесь в базе данных может не быть того случая, когда вы хотите обновить пользовательский интерфейс; вы можете просто CRUD для своей базы данных, вероятно, без изменений в пользовательском интерфейсе, и поэтому, возможно, Executors достаточно, хотя - person Zain; 28.05.2019

Относительно вопроса 2:

Для пользователей Kotlin теперь есть действительно хороший способ добиться этого, потому что, начиная с Room 2.1, есть прямая поддержка сопрограмм. Замечательный пример приведен здесь.

Вы можете использовать «функцию приостановки» непосредственно в DAO, которая заботится о том, чтобы ничего не выполнялось в основном потоке:

@Dao
 interface BarDao {

   @Query("SELECT * FROM bar WHERE groupId = 2")
   fun getAllBars(): LiveData<MutableList<Bar>>

   @Query( "SELECT * FROM bar WHERE groupId = 0 LIMIT 1")
   fun getRecentBar(): LiveData<Bar>

   @Insert
   suspend fun insert(bar: Bar)

   @Update
   suspend fun update(bar: Bar)

   @Delete
   suspend fun delete(bar: Bar)

}

тогда в вашем viewModel вы просто:

    fun insert(bar: Bar) = viewModelScope.launch {
        barDao.insert(bar)
    }

    fun update(bar: Bar) = viewModelScope.launch {
        barDao.update(bar)
    }

    fun delete(bar: Bar)= viewModelScope.launch {
        barDao.delete(bar)
    }
person Nulldroid    schedule 08.10.2019

Чтобы ответить на ваш второй вопрос:

Google разместил здесь Android Room Codelab, в котором изложил краткую архитектуру MVVM для реализации Room в Android:

Архитектура MVVM

Здесь рекомендуется, чтобы операции с базой данных обрабатывались общедоступным статическим ExecutorService в классе базы данных. Расположение класса ExecutorService может варьироваться, просто помните, что идея заключается в том, что в MVVM ваше представление не заботится о том, как данные фактически обрабатываются CURD, это ответственность ViewModel, а не View.

репозиторий github для этой лаборатории кода

Короче говоря, применить аналогичную идею к вашему коду можно примерно так:

class YourRepository {
    private FeedEntryDAO mFeedEntryDAO;

    YourRepository(Application application) {
        YourDatabase db = YourDatabase.getDatabase(application);
        mFeedEntryDAO = db.feedEntryDAO();
        mAllWords = mWordDao.getAlphabetizedWords();
    }


    void update(FeedEntry feedEntry) {
        Database.databaseExecutor.execute(() - > {
            mFeedEntryDAO.update(feedEntry);
        });
    }
}

class YourViewModel extends ViewModel {
    private YourRepository mRepository;
    void update(FeedEntry feedEntry) {
        mRepository.update(feedEntry)
    }
}

Делая это, ваше представление может напрямую вызывать viewModel.update (feedEntries [0]).

Следует упомянуть одну важную вещь: mFeedEntryDAO.update (feedEntry) автоматически запускает обратный вызов onChanged вашего наблюдателя в getFeedEntrys LiveData.

Это очень удобно в вашем случае. Подробнее о том, как срабатывает триггер, можно узнать здесь .

person Wei    schedule 29.05.2020

Вы также можете использовать аннотацию @Dao в абстрактных классах, поэтому:

  1. Создайте абстрактный @Dao BaseDao класс с абстрактными методами @Insert insert(entities) и с конкретным методом insert(entities, callback), который выполняет эту уродливую AsyncTask работу, вызывая абстрактный @Insert insert(entities) на onBackground и ваш обратный вызов на onPostExecute.
  2. Сделайте свой FeedEntryDAO также абстрактным, расширяйте BaseDao и @Query методы абстрактными.

Результат использования в Kotlin довольно приятный:

database.entityDao().insert(entities) { ids ->
    // success
}
person Allan Veloso    schedule 15.02.2019


Чтобы пользовательский интерфейс приложения обновлялся автоматически при изменении данных, используйте возвращаемое значение типа LiveData в описании метода запроса. Room генерирует весь необходимый код для обновления LiveData при обновлении базы данных.

@Dao
interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    fun loadUsersFromRegionsSync(regions: List<String>): LiveData<List<User>>
}

Примечание. Начиная с версии 1.0, Room использует список таблиц, доступных в запросе, чтобы решить, обновлять ли экземпляры LiveData.

person Malwinder Singh    schedule 06.04.2019