So i have this Dao where i have declared a LiveData Method.
#Dao
public interface EarthquakeDao {
#Insert
public void insert(Properties properties);
#Query("select * from properties")
public LiveData<List<Properties>> getAllProperties();
}
I have a sortByTitle method in my ViewModel.
public void sortByTitle(){
Collections.sort(propertiesList.getValue(), new Comparator<Properties>() {
#Override
public int compare(Properties o1, Properties o2) {
return o1.title.compareTo(o2.title);
}
});
propertiesList.postValue(featuresList.getValue());
}
Since the propertiesList is LiveData rather than MutableLiveData then i am not able to update the UI by using the postValue method.
In order to update the UI, i need to use MutableLiveData, and on the other hand...my database gives me a LiveData Object. So i am unable to achieve this.
Also, i cant make Dao's method a MutableLiveData, i know that.
Then how do i achieve this?
You could simply map the LiveData coming from Room. For example:
public LiveData<List<Properties>> properties = Transformations.map(propertiesList, list -> {
return sort(list);
})
See Transformations.map for more information
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 a conventional Room->DAO->Livedata->Repositiry->ViewModel->RecyclerView app. Different buttons of UI must pass different lists of data to RecyclerView.
By button click I want:
Make new #Query in DAO and get new LiveData<`List> object in return.
Put this new data into the RecyclerViewAdapter and call notifyDataSetChanged () to make new List visuals.
The Dao #Query:
#Query("SELECT * FROM entry_table WHERE path LIKE :path ORDER BY priority DESC")
LiveData<List<Entry>> getNotesOfFolder(String path); //Returns LiveData with List of Entries
The recyclerView is updated via onChanged of Observer like this:
public class RecyclerViewActivity extends AppCompatActivity {…
Observer<List<Entry>> entryObserver = new Observer<List<Entry>>() {
#Override
public void onChanged(List<Entry> entries) {
recyclerAdapter.setEntries(entries);
}
};
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.EntryHolder> {…
public void setEntries(List<Entry> entries) {
this.entries = entries; //setting LiveData content to adapter's List (i.e. entries)
notifyDataSetChanged();
The problem is that my Observer does not call the onChange method when LiveData receives new value from DAO. I believe it is because this LiveData’s content is not CHANGED but REPLACED by another LiveData.
I tried to re-subscribe the Observer to LiveData again and it somewhat worked, but when I try to call some conventional Room queries like #Delete, I got multiple (up to 10!) onChange calls and some of them behave weirdly and pass the wrong List to RVadapter.
So there two questions:
How can I just call onChanged() of my Observer?
Is there some other stylish way of passing new LiveData object to RecyclerView dynamically?
1)
In viewModel , create a getter method for live data:
//...
private LiveData<List<Entry>> liveData;
//...
public LiveData<List<Entry>> getLiveData() {
return liveData;
}
in Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
//...
viewModel.getLiveData().observe(this, new Observer<List<Entry>>() {
#Override
public void onChanged(List<Entry> entryList) {
//set new value here
}
});
}
2) DiffUtil is very helpful to update your list in recycler view and it gives you some nice animations.
I tried to re-subscribe the Observer to LiveData again and it somewhat worked, but when I try to call some conventional Room queries like #Delete, I got multiple (up to 10!) onChange calls and some of them behave weirdly and pass the wrong List to RVadapter.
This would make sense if you didn't first unsubscribe your observer from the old LiveData object... the one you replace when your query changes.
If your query updates, you will need to get a new LiveData from the DAO. If you overwrite your old LiveData with the new one, you will not only need to (re-)subscribe your Observer to the new one, you will also need to unsubscribe it from the old one. Otherwise it will live on and keep updating the observer.
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'm getting a LiveData> from a database in my view model. But I have to add some Foo-objects to the list, before I can forward them to the view.
I'm using the Room API to get access to a database. I'm using the recommended encapsulation with a Dao, a repository and the view model. The repository just forwards the LiveData from the Dao.
In the view model, I call the method from the repository and store the result in a variable. Because I can't use the observe-method of the LiveData-object, I tried it with the Transformations.map-method. But the map-method isn't called at any time.
public class FooViewModel extends AndroidViewModel {
private LiveData<List<Foo>> fromDatabase;
private MutableLiveData<List<Foo>> forView;
public FooViewModel(/*...*/) {
//...
forView = new MutableLiveData<>();
}
//Returns the LiveData<List> for the view, that should be observed
public LiveData<List<Foo>> getViewList() {
return forView;
}
//Loads the data from the database, modifies it and maps it to the LiveData for the view
public void loadFromDatabase(/*Some conditions for query*/) {
fromDatabase = repository.getData(/*Some conditions*/);
Transformations.map(fromDatabase, (foos) -> {
forView.setValue(fillList(foos));
return forView;
}
}
//Fills the list with some other foos
private static List<Foo> fillList(List<Foo> foos) {
//Fill the list
}
}
And in the view I observe the list in a way like this:
public class FooActivity {
protected void onCreate(/*Some inputs*/) {
viewModel.getViewList().observe(this, (foos) -> /*Display the list*/);
viewModel.loadFromDatabase(/*with some conditions*/);
}
}
And then nothing happens...
I tried also to forward the LiveData got from the repository and observe it. That observation works fine. But not the modified one.
//Loads the data from the database, modifies it and maps it to the LiveData for the view
public void loadFromDatabase(/*Some conditions for query*/) {
fromDatabase = repository.getData(/*Some conditions*/);
Transformations.map(fromDatabase, (foos) -> {
forView.setValue(fillList(foos));
return forView;
}
}
This will never work. fromDatabase is replaced but the transformation is done against the previous fromDatabase instance.
You need to set the query conditions into a MutableLiveData to which you do Transformations.switchMap to return the LiveData<List<T>> with the correct filters applied through the DAO.
Then if you modify the conditions live data, the DAO will re-evaluate the new list with the new conditions.
I am learning Mvvm pattern in android, and I don't understand one thing. How Live Data knows when data has changed in Room Database? I have this code:
Fragment:
newUserViewModel.getListItemById(itemId).observe(this, new Observer<User>() {
#Override
public void onChanged(#Nullable User user) {
tv.setText(user.getName());
}
});
View model:
public LiveData<User> getListItemById(String itemId){
return repository.getListItem(itemId);
}
Repository:
public LiveData<User> getListItem(String itemId){
return userDao.getUSerByID(itemId);
}
DAO:
#Query("SELECT * FROM User WHERE itemId = :itemId")
LiveData<User> getUSerByID(String itemId);// When this query gets executed and how live Data knows that our Table is changed?
let's say we inserted new User in Database. When is #Query("SELECT * FROM User WHERE itemId = :itemId") gets executed when there is new data in our database?) and How LiveData knows that we have new User in table and callback Observer owner that data has changed?
After diving in the Android Room code, I found out some things:
Room annotation processor generates code from Room annotations (#Query, #Insert...) using javapoet library
Depending on the result type of the query (QueryMethodProcessor), it uses a "binder" or another one. In the case of LiveData, it uses LiveDataQueryResultBinder.
LiveDataQueryResultBinder generates a LiveData class that contains a field _observer of type InvalidationTracker.Observer, responsible of listen to database changes.
Then, basically, when there is any change in the database, LiveData is invalidated and client (your repository) is notified.
Add to your Dao a query to be used just for notifications, something like:
#Query("SELECT * FROM my_table")
public LiveData<List<MyItem>> changeNotif();
and then in your activity listen to changes like this:
LiveData<List<MyItem>> items = AppDatabase.getAppDatabase().itemDao().changeNotif();
items.observe(this, new Observer<List<MyItem>>() {
#Override
public void onChanged(List<MyItem> myItems) {
}
});