How does LiveData know when data is changed in Room Database? - android

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

Related

Update UI With Live Data & Not MutableLiveData - Room Database

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

Is it a good practice to observeForever in Repository class? db+network paged list

Im building an app following architecture guidelines.Implemented room db caching + network.Need to get latest page number from separate entity.
My model:
#Entity(tableName = "top_rated_movie_page")
public class Top_Rated_Movies_Page {
#PrimaryKey(autoGenerate = true)
private int db_id;
private Integer page;
private Integer total_results;
private Integer total_pages;
#Ignore
private List<Result> results;
...
Result class contains data which i display in my paged list which observes changes from db.
Using PagedList.BoundaryCallback i need to fetch new data from network and insert it into db.
But i need to get page number somehow.
My dao:
#Insert
void insertAll(Top_Rated_Movies_Page page,List<Result> top_rated_results);
#Query("SELECT * FROM Result")
DataSource.Factory<Integer, Result> getAllResults();
#Query("SELECT * FROM top_rated_movie_page WHERE page= (SELECT MAX(page) FROM top_rated_movie_page)")
LiveData<Top_Rated_Movies_Page> getMoviePage();
I was thinking to observe Top_Rated_Movies_Page from db in my repository class with observeForever() to get that page number.
Is that the best way to approach this?
Since the only time you'll read the next page key or update the backing DB is through BoundaryCallback, you can just read / write your next page key directly.
So in your onItemAtEndLoad() implementation you want something like:
String nextMoviePage = db.runInTransaction(() -> {
movieDao.nextRemoteKey().key;
});
// Make sure not to run on main thread
MovieNetworkResponse response = networkApi.fetchNextMoviePage(remoteKey);
db.runInTransaction(() -> {
movieDao.clearAll(); // Remove previous key
movieDao.insertKey(RemoteKey(response.nextPageKey)); // Insert new key
movieDao.insertAll(response.movies); // Update DataSource + invalidate()
});
Your DAO:
#Insert
void insertAll(List<Result> top_rated_results);
#Query("SELECT * FROM Result")
DataSource.Factory<Integer, Result> getAllResults();
#Query("SELECT * FROM result_key LIMIT 1")
String nextRemoteKey();
#Insert
void insertKey(RemoteKey remoteKey);
And don't forget to clear out both the items and remoteKey whenever you expect to refresh the data!
In the case where you want to keep track of different keys for query, you can simply add that column to your RemoteKey entity.
FYI: Paging2 has been superseded by Paging3 (though just launched alpha01), here is the similar Paging3 sample which solves exactly your use-case: https://github.com/android/architecture-components-samples/blob/master/PagingWithNetworkSample/app/src/main/java/com/android/example/paging/pagingwithnetwork/reddit/repository/inDb/PageKeyedRemoteMediator.kt

in Room, Why I could #insert a row in a table but unable to retrieve it with SQL request?

I have a problem with room (my fist time with room).
I can insert some data in my DB but not retrieve something. I'm stuck with this problem. If you can explain me.
I explain :
I use Android Architecture Components. So I have an DAO to make my SQL requests. I have a repository to not handle my DB directly.
And finally, I have a viewModel to be not worry by the data persistance.
My DAO uses the annotation #Insert to create a row .
I use Stetho to look through my DB and everything is oK.
My row is created.
So, When I want to get it with another SQL request (by the same way DAO => repository =>ViewModel),
my request returns always null.
// --- DAO ---
#Query("SELECT * FROM users WHERE id = :id")
LiveData<RealEstate> getRealEstate(long id);
// --- REPOSITORY ---
private final RealEstateDao realEstateDao;
public RealEstateRepository(RealEstateDao realEstateDao) { this.realEstateDao = realEstateDao; }
public LiveData<RealEstate> getRealEstate(long id) { return this.realEstateDao.getRealEstate(id); }
// --- VIEWMODEL ---
public LiveData<RealEstate> getUser(long userId) {
return userDataSource.getRealEstate(userId);
}
I think that you are using incorrectly LiveData, Room have bridge adapter for RXJava/Kotlin, Coroutines, etc.
First, change the return value to something like this
#Query("SELECT * FROM users WHERE id = :id")
RealEstate getRealEstate(long id);
Remember that you cannot make database operations in the Main Thread, you should be using a mechanism for that RX, Coroutines, threads, etc.
After this, you can use LiveData in your viewmodel wrapping the data retrieved from your database to notify their UI parent.

Where to place logic when extracting data from database in mvvm pattern, using android room?

I am refactoring old application to mvvm pattern, using room, repository, viewmodel, ets.
I have an old code, which contains Content provider helper class with many functions like this:
public static int deleteOldLogs(int NumDays) {
//get NumDays before today, then constract a content provider delete command and run
...
}
or
public static Cursor getTodayLogs() {
//get a day from today, then constract a content provider query and run
...
}
or
public static boolean isActionValid(Context context, int id_order, int id_actionh) {
//get all products from database table, then check if all products match some criteria, then return boolean result
...
}
My question is in what layer to place this logic? Is it a repository or viewmodel should contain? All the examples that I see in the net is very simple and not suit my goals.
View model helps us to provide data between repository and UI . For direct interaction with room database , we use repository . Once we get the data from repo we can perform all sort of computation (i.e sorting , filtering etc ) in ViewModel .
In order to display data from the database, we use an observer who will observe the data changes, LiveData in the ViewModel.
We use ViewModelProvider which is going to create a ViewModel for us. We need to connect our ViewModel with the ViewModelProvider, and then in the onChanged method, we always get our updated data which we can display on the screen.
For eg . We want to get some record from our database .
For this we need to create a repository that will interact directly with database or carrying the logic to fetch data from database .
public class ABCRepository {
#Inject
DrugsDao mABCDao;
#Inject
public ABCRepository(){
}
public LiveData<List<NameModel>> getNameByLetter(String letter) {
return mABCDao.getName(letter);
}
}
Now in View Model
public class SearchViewModel extends ViewModel {
#Inject
ABCRepository mABCRepository;
LiveData<List<GlobalSearchModel>> getNameList(String queryText) {
MutableLiveData<List<GlobalSearchModel>> mGlobalSearchResults = new
MutableLiveData<>();
List<NameModel> synonymsNameList=mABCRepository.getNameByLetter(queryText);
new Thread(() -> {
List<GlobalSearchModel> globalSearchModelList =
mABCRepository.getNameByLetter(queryText)
// this is where you can perform any action on list . either sorting or.
filtering and then return the new list to your UI.
mGlobalSearchResults.postValue(globalSearchModelList);
}).start();
return globalSearchModelList;
}
}
In your fragment or activity you can observe this data ,
getViewModel().getAllCountries().observe(this, this::addSearchResultsInRecycler);
Hope this is helpful . Though not explained good but you can have reference from
https://medium.com/#skydoves/android-mvvm-architecture-components-using-the-movie-database-api-8fbab128d7

How to setup an application level livedata observer pattern

Background
I have multiple ways to get data from server, such as background polling and server push. These items may be contain same item.when data is ready, I insert these data into database with Android Room. when the insert item have exist in database, I will abort the item. I expect to get notify when these data insert success, so I use livedata in the Dao:
#Dao
public interface WordDao {
#Query("SELECT * from word_table where isNew = 0")
LiveData<List<Word>> getAlphabetizedWords();
#Insert
void insert(List<Word> word);
#Update
void update(List<Word> words);
}
In the application code:
#Override
public void onCreate() {
super.onCreate();
wordRepository = WordRepository.getInstance(this);
wordRepository.getAllWords().observeForever(new DatabaseObserver(this));
}
In the DatabaseObserver, I receive data change notify. I will handle these data, then I will set one property of the class(set isNew = 1) to indicate that the item has been handled. At the same time, that handle keep the later insert will not notify old insert data.
My Question
Is this pattern using observeForever() will be worked as expect?
When I write the demo code, I encounter that the DatabaseObserver's onChanged method receive same item list. After I change my code like this, the problem is still there. How to resolve this problem?

Categories

Resources