I'm currently refactoring legacy code to use Android Architecture Components and set up a room db and volley requests within a kind of repository pattern.
So the presentation/domain layer asks the repository to get LiveData-Objects to observe or tell him to synchronize with the server, after which old db entries are deleted and all current ones refetched from the server.
I've written tests for the synchronization part, so I'm sure, that the objects get fetched and inserted to the database correctly. But when writing a test to observe the entries of that db table (and test if the objects were saved correctly with everything there needs to be done before putting them into db) the LiveData> I'm observing, doesn't get triggered.
In the following snippet you can assume, that the synchronizeFormsWithServer(...) method does work correctly and is performing database operations asynchronously. It contains operations which deletes all Form-Objects from the db which are not present in the list of Forms fetched from the server and inserts all new ones. Since at the start of the test the database is empty this shouldn't matter that much
The test in which the observer doesn't get triggered:
#Test
public void shouldSaveFormsFromServerIntoDb() throws Exception
{
Lifecycle lifecycle = Mockito.mock(Lifecycle.class);
when(lifecycle.getCurrentState()).thenReturn(Lifecycle.State.RESUMED);
LifecycleOwner owner = Mockito.mock(LifecycleOwner.class);
when(owner.getLifecycle()).thenReturn(lifecycle);
final CountDownLatch l = new CountDownLatch(19);
formRepository.allForms().observe(owner, formList ->
{
if (formList != null && formList.isEmpty())
{
for (Form form : formList)
{
testForm(form);
l.countDown();
}
}
});
formRepository.synchronizeFormsWithServer(owner);
l.await(2, TimeUnit.MINUTES);
assertEquals(0, l.getCount());
}
The FormRepository code:
#Override
public LiveData<List<Form>> allForms()
{
return formDatastore.getAllForms();
}
The datastore:
#Override
public LiveData<List<Form>> getAllForms()
{
return database.formDao().getAllForms();
}
The formDao code (database is implemented how you'd expect it from room):
#Query("SELECT * FROM form")
LiveData<List<Form>> getAllForms();
It may very well be, that I didn't understand something about the LiveData-Components, because this is my first time using them, so maybe I got something fundamentally wrong.
Every bit of help is very much appreciated :)
PS: I stumbled across THIS post, which discusses a similar issue, but since I'm currently not using DI at all and just use a single instance of the formrepository (which has only one instance of formDao associated) I don't think it's the same problem.
Ok, so I found the solution, although I don't know, why it behaves that way.
Remember when I said "don't worry about the synchronize method"? Well... turns out there were a couple of things wrong with it, which delayed the solution further.
I think the most important error there was the method to update the objects in the database when the network response came in.
I used to call
#Update
void update(Form form)
in the dao, which for unknown reasons doesn't trigger the LiveData-Observer. So I changed it to
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Form form);
After doing this I could get the Form-LiveData from my repository as easy as
LiveData<List<Form>> liveData = formRepository.allForms();
Then subscribe to it as usual.
The previously failed test looks like this now:
#Test
public void shouldSaveFormsFromServerIntoDb() throws Exception
{
Lifecycle lifecycle = Mockito.mock(Lifecycle.class);
when(lifecycle.getCurrentState()).thenReturn(Lifecycle.State.RESUMED);
LifecycleOwner owner = Mockito.mock(LifecycleOwner.class);
when(owner.getLifecycle()).thenReturn(lifecycle);
final CountDownLatch l = new CountDownLatch(19);
final SortedList<Form> sortedForms = new SortedList<Form>(Form.class, new SortedList.Callback<Form>()
{
#Override
public int compare(Form o1, Form o2)
{
return o1.getUniqueId().compareTo(o2.getUniqueId());
}
#Override
public void onChanged(int position, int count)
{
Log.d(LOG_TAG, "onChanged: Form at position " + position + " has changed. Count is " + count);
for (int i = 0; i < count; i++)
{
l.countDown();
}
}
#Override
public boolean areContentsTheSame(Form oldItem, Form newItem)
{
return (oldItem.getContent() != null && newItem.getContent() != null && oldItem.getContent().equals(newItem.getContent())) || oldItem.getContent() == null && newItem.getContent() == null;
}
#Override
public boolean areItemsTheSame(Form item1, Form item2)
{
return item1.getUniqueId().equals(item2.getUniqueId());
}
#Override
public void onInserted(int position, int count)
{
}
#Override
public void onRemoved(int position, int count)
{
}
#Override
public void onMoved(int fromPosition, int toPosition)
{
}
});
LiveData<List<Form>> ld = formRepository.allForms();
ld.observe(owner, formList ->
{
if (formList != null && !formList.isEmpty())
{
Log.d(LOG_TAG, "shouldSaveFormsFromServerIntoDb: List contains " + sortedForms.size() + " Forms");
sortedForms.addAll(formList);
}
});
formRepository.synchronizeFormsWithServer(owner);
l.await(2, TimeUnit.MINUTES);
assertEquals(0, l.getCount());
}
I know that exactly 19 Forms will get fetched from the server and then every Form will get changed once (first time I load a list containing all Forms with reduced data, and the second time I load every item from the server again replacing the old value in the db with the new value with more data).
I don't know if this will help you #joao86 but maybe you have a similar issue. If so, please make sure to comment here :)
You have to use the same database instance at all places.
=> Use a singleton for that
I had a similar issue with yours --> LiveData is not updating its value after first call
Instead of using LiveData use MutableLiveData and pass the MutableLiveData<List<Form>> object to the Repository and do setValue or postValue of the new content of the list.
From my experience with this, which is not much, apparently the observer is connected to object you first assign it too, and every change must be done to that object.
Related
I'm using LiveData with MVVM. After updating my database with Room, I am trying to sendback both the Object I inserted into my Room database, and also the adapter position. In my ViewModel class, the method is:
private MutableLiveData<String> insertItemLiveData = new MutableLiveData<>;
public void insertMenuItem(MenuItem menuItem, int adapterPositionToUpdate){
repo.insertOrder(menuItem.getId())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(#NonNull Disposable d) {
}
#Override
public void onSuccess(#NonNull Integer integer) {
//The order is successfully inserted into database
//So I return back the name of the inserted order
String s = "Inserted Item: " + menuItem.getNameOfOrder();
insertItemLiveData.setValue(s);
}
#Override
public void onError(#NonNull Throwable e) {
errorLiveData.setValue("Failed to cancel order.");
}
});
}
In the on success method, it returns the String I want to display, but I also want to update the position of the Recyclerview item that has changed. What is the best way to handle this situation?
I can use a wrapper class and have setters for a String and the adapter position, but I feel like there's probably a better way to do this.
A resource wrapper is a good idea for it.MVVM Resource Wrapper With Live Data you can check my code to get an insight on how to use it
I have Boolean booleanCheckAvailabilityData to check availability data in my activity to create add/remove favorite. then i create
dataFavoriteMovieById = favoriteMovieViewModel.getAllFavoriteMovieById(idMovie);
to get data by id. so i make conditional statement to check avaiability data then put the result to boolean and i use the boolean later to add or remove the favorite.
if (dataFavoriteMovieById == null) {
booleanCheckAvailabilityData = false;
} else {
booleanCheckAvailabilityData = true;
}
In the first run, it work. my dataFavoriteMovieById is null
But, after i add or remove favorite. it always always contains data (RoomTrackingLiveData).
How can i solve this...
my code link : https://github.com/komangss/Submission-Menjadi-Android-Developer-Expert/blob/master/app/src/main/java/com/dicoding/submissionmade2_1/activity/DetailMovieActivity.java
I played with your app (thanks for providing a github link) and here are my results.
Latest app version
Your latest implementation doesn't produce an NPE anymore since you use getAllFavoriteMovieById in a more consistent way. You no longer initialize a LiveData instance in FavoriteMovieRepository by yourself but delegate it to Room to do it for you. So, you won't get an NPE since Room will always create a list to return results. If there're no items, it will return an empty list. So, you can safely remove a try/catch here:
try {
favoriteMovieViewModel.getAllFavoriteMovieById(idMovie).observe(this, new Observer<List<FavoriteMovie>>() {
#Override
public void onChanged(List<FavoriteMovie> favoriteMovies) {
booleanCheckAvailabilityData = favoriteMovies.size() != 0;
}
});
} catch (NullPointerException e) {
Log.d("ini bug nya", e.getMessage());
}
Original app version
In addition to what #Paul Ost said about how favoriteMovieViewModel should be used properly (by listening to it, not using it directly), I will explain why you actually had an NPE.
In that version, you were running into a NullPointerException because you returned the favoriteMovieById LiveData before it was actually initialized in your GetFavoriteMovieByIdAsyncTask.
So, here what was happening in detail. First, once your DetailMovieActivity had been created, favoriteMovieViewModel called getAllFavoriteMovieById() as below:
DetailMovieActivity.java
...
favoriteMovieViewModel = ViewModelProviders.of(this).get(FavoriteMovieViewModel.class);
dataFavoriteMovieById = favoriteMovieViewModel.getAllFavoriteMovieById(idMovie);
...
FavoriteMovieViewModel.java
FavoriteMovieViewModel instance, in turn, delegated the call to FavoriteMovieRepository instance as below:
public LiveData<List<FavoriteMovie>> getAllFavoriteMovieById(int idMovie) {
return repository.getFavoriteMovieById(idMovie);
}
FavoriteMovieRepository.java
Finally, getFavoriteMovieById started a GetFavoriteMovieByIdAsyncTask and returned favoriteMovieById:
public LiveData<List<FavoriteMovie>> getFavoriteMovieById(int id_movie) {
new GetFavoriteMovieByIdAsyncTask(favoriteMovieDao).execute(id_movie);
return favoriteMovieById;
}
But that's wrong, since your favoriteMovieById was set to null by default, and so on the first run, you were always getting it.
Your AsyncTask was eventually setting a non-null value, but it was too late:
...
private static class GetFavoriteMovieByIdAsyncTask extends AsyncTask<Integer, Void, Void> {
...
#Override
protected Void doInBackground(Integer... integers) {
FavoriteMovieRepository.favoriteMovieById = favoriteMovieDao.getFavoriteMovieById(integers[0]);
return null;
}
}
...
From what I can see in your code - getAllFavoriteMovieById works as expected. The thing is - you are using LiveData as a return type of getAllFavoriteMovieById thus it returns not the value itself but a LiveData wrapper. But if you will try to observe this LiveData object you will(presumably since I haven't seen relevant code) receive null instead of favourite value. The only correct place to assign value to your booleanCheckAvailabilityData inside this observer(depending on your DAO code of course).
favouriteMovieViewModel.getAllFavoriteMovieById().observe(this, Observer { data ->
if (data == null) {
booleanCheckAvailabilityData = false;
} else {
booleanCheckAvailabilityData = true;
}
})
Something like that(once again it depends on your DAO code and getAllFavoriteMovieById implementation)
Hope it helps.
In ROOM Try Deleting the old TABLE before inserting the new data. In that case the old data will be deleted as we are deleting the old data
With android Paging library it is really easy to load data from Database in chunks and ViewModel provides automatic UI update and data survival. All these frameworks modules help us create a great app in android platform.
A typical android app has to show a list of items and allows user to search that list. And this what I want to achieve with my app. So I have done an implementation by reading many documentations, tutorials and even stackoverflow answers. But I am not so sure whether I am doing it correctly or how I supposed to do it. So below, I have shown my way of implementing paging library with ViewModel and RecyclerView.
Please, review my implementation and correct me where I am wrong or show me how I supposed to do it. I think there are many new android developers like me are still confused how to do it correctly as there is no single source to have answers to all your questions on such implementation.
I am only showing what I think is important to show. I am using Room. Here is my Entity that I am working with.
#Entity(tableName = "event")
public class Event {
#PrimaryKey(autoGenerate = true)
public int id;
public String title;
}
Here is DAO for Event entity.
#Dao
public interface EventDao {
#Query("SELECT * FROM event WHERE event.title LIKE :searchTerm")
DataSource.Factory<Integer, Event> getFilteredEvent(String searchTerm);
}
Here is ViewModel extends AndroidViewModel which allows reading and searching by providing LiveData< PagedList< Event>> of either all events or filtered event according to search text. I am really struggling with the idea that every time when there is a change in filterEvent, I'm creating new LiveData which can be redundant or bad.
private MutableLiveData<Event> filterEvent = new MutableLiveData<>();
private LiveData<PagedList<Event>> data;
private MeDB meDB;
public EventViewModel(Application application) {
super(application);
meDB = MeDB.getInstance(application);
data = Transformations.switchMap(filterEvent, new Function<Event, LiveData<PagedList<Event>>>() {
#Override
public LiveData<PagedList<Event>> apply(Event event) {
if (event == null) {
// get all the events
return new LivePagedListBuilder<>(meDB.getEventDao().getAllEvent(), 5).build();
} else {
// get events that match the title
return new LivePagedListBuilder<>(meDB.getEventDao()
.getFilteredEvent("%" + event.title + "%"), 5).build();
}
}
});
}
public LiveData<PagedList<Event>> getEvent(Event event) {
filterEvent.setValue(event);
return data;
}
For searching event, I am using SearchView. In onQueryTextChange, I wrote the following code to search or to show all the events when no search terms is supplied meaning searching is done or canceled.
Event dumpEvent;
#Override
public boolean onQueryTextChange(String newText) {
if (newText.equals("") || newText.length() == 0) {
// show all the events
viewModel.getEvent(null).observe(this, events -> adapter.submitList(events));
}
// don't create more than one object of event; reuse it every time this methods gets called
if (dumpEvent == null) {
dumpEvent = new Event(newText, "", -1, -1);
}
dumpEvent.title = newText;
// get event that match search terms
viewModel.getEvent(dumpEvent).observe(this, events -> adapter.submitList(events));
return true;
}
Thanks to George Machibya for his great answer. But I prefer to do some modifications on it as bellow:
There is a trade off between keeping none filtered data in memory to make it faster or load them every time to optimize memory. I prefer to keep them in memory, so I changed part of code as bellow:
listAllFood = Transformations.switchMap(filterFoodName), input -> {
if (input == null || input.equals("") || input.equals("%%")) {
//check if the current value is empty load all data else search
synchronized (this) {
//check data is loaded before or not
if (listAllFoodsInDb == null)
listAllFoodsInDb = new LivePagedListBuilder<>(
foodDao.loadAllFood(), config)
.build();
}
return listAllFoodsInDb;
} else {
return new LivePagedListBuilder<>(
foodDao.loadAllFoodFromSearch("%" + input + "%"), config)
.build();
}
});
Having a debouncer helps to reduce number of queries to database and improves performance. So I developed DebouncedLiveData class as bellow and make a debounced livedata from filterFoodName.
public class DebouncedLiveData<T> extends MediatorLiveData<T> {
private LiveData<T> mSource;
private int mDuration;
private Runnable debounceRunnable = new Runnable() {
#Override
public void run() {
DebouncedLiveData.this.postValue(mSource.getValue());
}
};
private Handler handler = new Handler();
public DebouncedLiveData(LiveData<T> source, int duration) {
this.mSource = source;
this.mDuration = duration;
this.addSource(mSource, new Observer<T>() {
#Override
public void onChanged(T t) {
handler.removeCallbacks(debounceRunnable);
handler.postDelayed(debounceRunnable, mDuration);
}
});
}
}
And then used it like bellow:
listAllFood = Transformations.switchMap(new DebouncedLiveData<>(filterFoodName, 400), input -> {
...
});
I usually prefer to use DataBiding in android. By using two way Data Binding you don't need to use TextWatcher any more and you can bind your TextView to the viewModel directly.
BTW, I modified George Machibya solution and pushed it in my Github. For more details you can see it here.
I will strong advice to start using RxJava and you it can simplify the entire problem of looking on the search logic.
I recommend in the Dao Room Class you implement two method, one to query all the data when the search is empty and the other one is to query for the searched item as follows. Datasource is used to load data in the pagelist
#Query("SELECT * FROM food order by food_name")
DataSource.Factory<Integer, Food> loadAllFood();
#Query("SELECT * FROM food where food_name LIKE :name order by food_name")
DataSource.Factory<Integer, Food> loadAllFoodFromSearch(String name);
In the ViewModel Class we need to two parameter that one will be used to observed searched text and that we use MutableLiveData that will notify the Views during OnChange. And then LiveData to observe the list of Items and update the UI.
SwitchMap apply the function that accept the input LiveData and generate the corresponding LiveData output. Please find the below Code
public LiveData<PagedList<Food>> listAllFood;
public MutableLiveData<String> filterFoodName = new MutableLiveData<>();
public void initialFood(final FoodDao foodDao) {
this.foodDao = foodDao;
PagedList.Config config = (new PagedList.Config.Builder())
.setPageSize(10)
.build();
listAllFood = Transformations.switchMap(filterFoodName, outputLive -> {
if (outputLive == null || outputLive.equals("") || input.equals("%%")) {
//check if the current value is empty load all data else search
return new LivePagedListBuilder<>(
foodDao.loadAllFood(), config)
.build();
} else {
return new LivePagedListBuilder<>(
foodDao.loadAllFoodFromSearch(input),config)
.build();
}
});
}
The viewModel will then propagate the LiveData to the Views and observe the data onchange. In the MainActivity then we call the method initialFood that will utilize our SwitchMap function.
viewModel = ViewModelProviders.of(this).get(FoodViewModel.class);
viewModel.initialFood(FoodDatabase.getINSTANCE(this).foodDao());
viewModel.listAllFood.observe(this, foodlistPaging -> {
try {
Log.d(LOG_TAG, "list of all page number " + foodlistPaging.size());
foodsactivity = foodlistPaging;
adapter.submitList(foodlistPaging);
} catch (Exception e) {
}
});
recyclerView.setAdapter(adapter);
For the first onCreate initiate filterFoodName as Null so that to retrieve all items.
viewModel.filterFoodName.setValue("");
Then apply TextChangeListener to the EditText and call the MutableLiveData that will observe the Change and update the UI with the searched Item.
searchFood.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i,
int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int
i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
//just set the current value to search.
viewModel.filterFoodName.
setValue("%" + editable.toString() + "%");
}
});
}
Below is my github repo of full code.
https://github.com/muchbeer/PagingSearchFood
Hope that help
I am unable to get a LiveData ArrayList from a Room database but I am able to retrieve a standard ArrayList and cannot figure out why.
I have run this code in debug mode and the ArrayList returns a size of 4, which it should. The LiveData ArrayList, when get value is used returns null. I have run the LiveData query both within an executor and outside of the executor and it returns null.
Declarations
public LiveData<List<CourseEntity>> courseEntities;
private List<CourseEntity> courseData = new ArrayList<>();
Code outside of executor
public void loadData(final int termId) {
courseEntities = courseRepository.getCourseByTermId(termId);
courseData = courseEntities.getValue();
}
Code inside executor
public void loadData(final int termId) {
executor.execute(new Runnable() {
#Override
public void run() {
courseEntities = courseRepository.getCourseByTermId(termId);
courseData = courseEntities.getValue();
}
});
}
Code using just an ArrayList
public void loadData(final int termId) {
executor.execute(new Runnable() {
#Override
public void run() {
courseData = courseRepository.getCourseByTerm(termId);
}
});
}
Queries from Dao
#Query("SELECT * FROM course " +
"WHERE term_id = :termIdSelected ORDER BY course_start" )
LiveData<List<CourseEntity>> getCourseByTermId(int termIdSelected);
#Query("SELECT * FROM course WHERE term_id = :termIdSelected ORDER BY course_start")
List<CourseEntity> getCourseByTerm(int termIdSelected);
This produces a null value for the LiveData instead of a value of 4 like the plain ArrayList produces. The only difference being the LiveData wrapper for the result. Any wisdom someone can share would be most appreciated.
When you have a Room #Dao return a LiveData (or an RxJava type like Observable or Single), the generated implementation will do the actual work on a background thread. So, when getCourseByTermId() returns, the work will not yet have begun, so the LiveData will not have results yet.
Reactive types, like LiveData, are meant to be observed. So, your activity/fragment/whatever would observe() the LiveData and react to the result when it is delivered.
I have LiveData for Books in ViewModel's constructor:
LiveData<List<Book>> books;
public MyViewModel(#NonNull Application application) {
super(application);
books = bookRepository.getBooks();
}
When user creates new book from UI, I want attribute book_order to be filled with incremented maximum of book_order of other books. To better describe what I want, see following preudocode:
book.book_order = max(books.book_order) + 1;
So when there are three books with book_order 1, 2, 3 respectively, new book would have this attribute set to 4.
Question is, how can I do this with LiveData in ViewModel? I tried using Transformations.map to the new LiveData, but this approach is not working at all, bookMax seems to be 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);
}
Any ideas how to set incremented maximum to the new book? It can be another approach than the one described here. ViewModel was created to separate app logic from UI. However it does not seem to do that in this case, because if I want to observe value, I need to be in Activity. Also, I did not find any alternative how to do this kind of getting one value from DB. Any help appreciated.
Note that your books are livedata, thus may change its value from time to time.
Whereis your bookMax is a single value that should be calculated at the moment of insertion.
To insert you need:
get the current books list
then calculate bookMax
then actually insert.
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
EDIT Here is Java code
// 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);
Using LiveData will not help in your scenario.
LiveData in DAO gets executed on a different thread and the new value is posted in observer code or in your case Transformation.map() callback. So you need to access book.id inside Transformation.map().
However, if you insert book in Transformation.map(), it would trigger an infinite loop since on every table entry update Transformation.map() would be called as LiveData> would change.
So, for your case:
Expose a method which exposes last book id
Insert a new entry.
Add a LiveData> to receive an update and display in UI.
Instead of taking all books for finding max book order, you should make a method in your repository that will provide you max number of book_order from db.
Something like below pseudo code :
int maxOrder = bookRepository.getMaxBookOrder();
Now, all you need to do is while inserting new book, you can use that maxOrder variable to incremental purpose.
So, your insert method will be like :
public void insertBook(Book book) {
int maxOrder = bookRepository.getMaxBookOrder();
book.setBookOrder(maxOrder + 1)
bookRepository.update(book);
}
Here, assuming that you're using ROOM for persisting database, this is the query that can help you get your maximum book_order:
SELECT MAX(book_order) FROM Book
If ROOM isn't your case then, you can do with another approach :
We first retrieve list using repository method and then find maximum from it like below pseudo :
List<Book> books = bookRepository.getBooks().getValue(); // Assuming getBooks() returns LiveData
Then find max from it and then increment it by one :
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);
}
Even you can move this code of finding maximum to repository method as mentioned earlier like :
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;
}
If you really want to do it with a LiveData you can create a custom one:
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)
}
}
Then you can just create it and use:
val data = bookRepository.getBooks() // Make getBooks() return BooksLiveData now
data.addBook(userCreatedBook) // This'll trigger observers as well
You can still observe this live data, since it's posting initialList when a book is added, it'll notify observers. You can change it more, for example, to return the book that's added etc.
Side note: It might be better to extend from MutableLiveData instead, since LiveData is not supposed to update its value but internally you're posting something so it might be confusing.
Approach 1:
You can use an Auto Increment field or the PrimaryKey such as an id for the book_order's functionality. You can even name it book_order if you want to. Make your Model or Entity class Like:
public class Book{
#PrimaryKey(autoGenerate = true)
private int book_order;
//other data members, constructors and methods
}
So that, the the book_order gets incremented on each Book added to the database.
Next you can have your ViewModel classs like:
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();
}
}
Now you can subscribe your list Activity to this ViewModel (ie. make your activity observe the ViewModel) by putting the call to following method in your activity's 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
}
Doing these things, you will be able to receive updates, ie. whenever a Book is added to your database by the user, the List/ Recycler view should automatically display the newly added Book.
Approach 2:
If this is not what you have wanted at all and you only want to find the latest added book's order, you can skip the third code block completely and use the following in your Dao:
#Query("SELECT COUNT(*) FROM books")
int getCount();
which gives the total number of books ie. rows in the books table, which you can then call from your repository, which in turn can be called from your ViewModel which in turn can be called from the Activity.
Approach 3:
If you want the book_order which I think is the latest number of books in the database after a new book is added, you can use Approach 1 which gives you the List of Book in the ViewModel. You can then get the number of books from the booklist count.
Important!
either way you would want to edit your insertBook() method in your editor or newBook ViewModel and make it something like:
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);
}
and in your Repository corresponding insert would look like:
public void insertBook(final Book book) {
executor.execute(new Runnable() {
#Override
public void run() {
mDb.bookDao().insertBook(book);
}
});
}
and corresponding Dao method:
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insertBook(Book book);