Получите одно значение из LiveData

У меня LiveData для книг в конструкторе ViewModel:

LiveData<List<Book>> books;

public MyViewModel(@NonNull Application application) {
    super(application);
    books = bookRepository.getBooks();
}

Когда пользователь создает новый book из пользовательского интерфейса, я хочу, чтобы атрибут book_order был заполнен увеличенным максимумом book_order других книг. Чтобы лучше описать то, что я хочу, см. Следующий предварительный код:

book.book_order = max(books.book_order) + 1;

Поэтому, когда есть три книги с book_order 1, 2, 3 соответственно, для новой книги этот атрибут будет установлен на 4.

Вопрос в том, как это сделать с LiveData в ViewModel? Я пробовал использовать Transformations.map с новым LiveData, но этот подход вообще не работает, bookMax кажется null.

public void insertBook(Book book) {
    LiveData<Integer> bookMax = Transformations.map(books,  
        list -> {
          int value = 0;
          for(Book b: list) {
              if (value < b.getBookOrder()) {
                 value = b.getBookOrder();
              }
          }
        }
    );
    book.setBookOrder(bookMax + 1)
    bookRepository.update(book);
}

Есть идеи, как установить увеличенный максимум для новой книги? Это может быть другой подход, нежели описанный здесь. ViewModel был создан для отделения логики приложения от пользовательского интерфейса. Однако в данном случае, похоже, этого не происходит, потому что, если я хочу наблюдать значение, мне нужно быть в Activity. Кроме того, я не нашел альтернативы, как получить одно значение из БД. Любая помощь приветствуется.


person Tom11    schedule 13.04.2019    source источник
comment
Здравствуйте, я могу ошибаться, но похоже, что вы можете использовать поле id объекта Book. Для меня функциональность book_order может быть полностью заменена идентификатором модели. А при работе с LiveData вы можете подписать свое Activity на ViewModel, который имеет ссылку на ваш LiveData, который должен автоматически отображать все книги в вашем главном макете потока Master-Detail. Сообщите мне, может ли что-то подобное сработать, и я отправлю ответ с некоторым кодом   -  person ravi    schedule 23.04.2019


Ответы (5)


Обратите внимание, что ваши books livedata, поэтому время от времени может изменять свое значение.
Где bookMax - это одно значение, которое должно быть вычислено в момент вставки .

Для вставки нужно:

  • получить текущий список книг
  • затем рассчитайте bookMax
  • затем собственно вставьте.
val bookList: List<Book> = books.value   // current value. may be null!

val bookMax: Int = bookList.maxBy { it.order }.order   // find max order

// insert
val newBooks = arrayListOf(bookList)
newBooks.add(newBook)

books.value = newBooks  // update your livedata

ИЗМЕНИТЬ Вот код Java

// get current value. may be null!
List<Book> bookList = books.getValue();
// so we better handle it early
if (bookList == null) {
    bookList = new ArrayList<>();
}

// calculate max order
int maxOrder = -1;
for (Book book : bookList) {
    if (maxOrder < book.order) {
        maxOrder = book.order;
    }
}

// create new book
Book newBook = new Book();
newBook.order = maxOrder + 1;

// add book to the list
bookList.add(newBook);

// do with new list whatever you want
// for example, you can update live data (if it is a MutableLiveData)
books.setValue(bookList);
person dhabensky    schedule 15.04.2019

Использование LiveData не поможет в вашем сценарии.

LiveData в DAO выполняется в другом потоке, а новое значение публикуется в коде наблюдателя или, в вашем случае, обратном вызове Transformation.map (). Итак, вам нужно получить доступ к book.id внутри Transformation.map ().

Однако, если вы вставите книгу в Transformation.map (), это вызовет бесконечный цикл, поскольку при каждом обновлении записи таблицы Transformation.map () будет вызываться при изменении LiveData>.

Итак, для вашего случая:

  1. Выставить метод, который предоставляет последний идентификатор книги
  2. Вставьте новую запись.
  3. Добавьте LiveData>, чтобы получать обновления и отображать их в пользовательском интерфейсе.
person Nilesh Tiwari    schedule 20.04.2019

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

Что-то вроде псевдокода ниже:

int maxOrder = bookRepository.getMaxBookOrder();

Теперь все, что вам нужно сделать, это вставить новую книгу, вы можете использовать эту переменную maxOrder для дополнительных целей.

Итак, ваш метод вставки будет таким:

public void insertBook(Book book) {
    int maxOrder = bookRepository.getMaxBookOrder();
    book.setBookOrder(maxOrder + 1)
    bookRepository.update(book);
}

Предполагая, что вы используете ROOM для постоянной базы данных, это запрос, который может помочь вам получить максимальное book_order:

SELECT MAX(book_order) FROM Book

Если ROOM не в вашем случае, вы можете использовать другой подход:

Сначала мы извлекаем список, используя метод репозитория, а затем находим из него максимум, как показано ниже:

List<Book> books = bookRepository.getBooks().getValue(); // Assuming getBooks() returns LiveData

Затем найдите из него max и затем увеличьте его на единицу:

public void insertBook(Book book) {
    List<Book> books = bookRepository.getBooks().getValue();
    // calculating max order using loop
    int maxOrder = -1;
    for (Book book : books) {
        if (maxOrder < book.order) {
            maxOrder = book.order;
        }
    }
    book.setBookOrder(maxOrder + 1)
    bookRepository.update(book);
}

Даже вы можете переместить этот код поиска максимума в метод репозитория, как упоминалось ранее, например:

public int getMaxBookOrder() {
    List<Book> books = getBooks().getValue();
    // calculating max order using loop
    int maxOrder = -1;
    for (Book book : books) {
        if (maxOrder < book.order) {
            maxOrder = book.order;
        }
    }
    return maxOrder;
}
person Jeel Vankhede    schedule 23.04.2019

Если вы действительно хотите сделать это с LiveData, вы можете создать собственный:

class BooksLiveData(list: List<Book>) : LiveData<List<Book>>() {

    val initialList: MutableList<Book> = list.toMutableList()

    fun addBook(book: Book) {
        with(initialList) {
            // Assuming your list is ordered
            add(book.copy(last().bookOrder + 1))
        }

        postValue(initialList)
    }
}

Затем вы можете просто создать его и использовать:

val data = bookRepository.getBooks() // Make getBooks() return BooksLiveData now
data.addBook(userCreatedBook) // This'll trigger observers as well

Вы все еще можете наблюдать за этими данными в реальном времени, поскольку он публикует initialList при добавлении книги, он уведомит наблюдателей. Вы можете изменить его больше, например, чтобы вернуть добавленную книгу и т. Д.

Дополнительное примечание. Возможно, лучше было бы вместо этого перейти от MutableLiveData, поскольку LiveData не должен обновлять свое значение, но внутри вы что-то публикуете, поэтому это может сбивать с толку.

person Mel    schedule 23.04.2019

Подход 1: вы можете использовать поле Auto Increment или PrimaryKey, например id, для функций book_order. Вы даже можете назвать его book_order, если хотите. Сделайте свою модель или класс сущности следующим образом:

public class Book{
    @PrimaryKey(autoGenerate = true)
    private int book_order;
    //other data members, constructors and methods
}

Таким образом, book_order увеличивается при каждом добавлении Book в базу данных.

Затем вы можете иметь свои ViewModel классы, например:

public class MyViewModel extends AndroidViewModel {
    private BookRepository bookRepository;
    LiveData<List<Book>> books;

    public MyViewModel(@NonNull Application application) {
        super(application);
        bookRepository = AppRepository.getInstance(application.getApplicationContext());
        books = bookRepository.getBooks();
    }

}

Теперь вы можете подписать свой список Activity на эту ViewModel (т. Е. Сделать так, чтобы ваша активность соответствовала ViewModel), поместив вызов следующего метода в onCreate() вашей активности:

private void initViewModel() {
    final Observer<List<Book>> bookObserver= new Observer<List<Book>>() {
        @Override
        public void onChanged(@Nullable List<Book> books) {
            bookList.clear();
            bookList.addAll(books);

            if (mAdapter == null) {
                mAdapter = new BooksAdapter(bookList, YourActivity.this);
                mRecyclerView.setAdapter(mAdapter);
            } else {
                mAdapter.notifyDataSetChanged();
            }
        }
    };

    mViewModel = ViewModelProviders.of(this)
            .get(MyViewModel.class);

    mViewModel.mBooks.observe(this, notesObserver); //mBooks is member variable in ViewModel class
}

Выполняя эти действия, вы сможете получать обновления, т.е. всякий раз, когда Book добавляется в вашу базу данных пользователем, в представлении List / Recycler должно автоматически отображаться только что добавленное Book.

Подход 2. Если это совсем не то, что вам нужно, и вы хотите найти только последний добавленный заказ на книгу, вы можете полностью пропустить третий блок кода и использовать следующее в своем Dao:

@Query("SELECT COUNT(*) FROM books")
int getCount();

что дает общее количество книг, т.е. строк в таблице книг, которую затем можно вызвать из своего репозитория, который, в свою очередь, можно вызвать из модели ViewModel, которая, в свою очередь, может быть вызвана из Activity.

Подход 3: если вам нужно book_order, которое, как мне кажется, является последним количеством книг в базе данных после добавления новой книги, вы можете использовать подход 1, который дает вам список книг в модели представления. Затем вы можете получить количество книг из списка книг.

Важно! в любом случае вы захотите отредактировать свой insertBook() метод в редакторе или в новой модели просмотра книги и сделать его примерно таким:

public void insertBook(Book book) {
    Book book= mLiveBook.getValue(); //declare mLiveBook as MutableLiveData<Book> in this ViewModel

//maybe put some validation here or some other logic

    mRepository.insertBook(book);
}

и в вашем репозитории соответствующая вставка будет выглядеть так:

public void insertBook(final Book book) {
    executor.execute(new Runnable() {
        @Override
        public void run() {
            mDb.bookDao().insertBook(book);
        }
    });
}

и соответствующий метод Дао:

@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertBook(Book book);
person ravi    schedule 23.04.2019