Suppose I have got a list of data in the Room DB.
Let the data be: setId, formId, formName.
There can be multiple formId in the single setId. Let setId 1 contains 10 forms, 2 contains 5 forms.
Now what I wanna do is, extract the data from the db using the setId in the ViewModel.
Let my dao be:
#Query("SELECT * FROM form WHERE setId = :id")
LiveData<List<Form>> getAllFilledForms(int id);
How can I implement such action in ViewModel.
I want to retrieve all the list of the forms where the set id is same, let 1.
Edit:
ViewModel Class:
public class ListSetViewModel extends AndroidViewModel {
private final LiveData<List<FormSet>> allFormSets;
private FormDatabase formDatabase;
public ListSetViewModel(#NonNull Application application) {
super(application);
formDatabase = FormDatabase.getDatabase(application);
allFormSets = formDatabase.formSetDao().getAllFilledForms(setId);
}
public LiveData<List<FormSet>> getAllFormSets(setId){
return allFormSets;
}
}
You need to inject your ViewModel with an ID by a Factory or Dagger2. Or you can use a public method to get data.
public LiveData<List<FormSet>> getAllFormSets(setId){
return allFormSets = formDatabase.formSetDao().getAllFilledForms(setId);
}
Related
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
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
I have created following classes in realm for entities like employee , Student and storing its sync status in SyscInfo class
Employee extends RealmObject
_id
name
phone
address
isSync
Student extends RealmObject
_id
name
phone
isSync
SyncInfo extends RealmObject
isSync
timestamp
Now if any emploee or student records are not synced i am setting syncInfo.isSync to false
my background scheduler will check whether we have any offline / not synced data stored in realm db. for that i am firing query for employee and student both.
RealmList<Employee> offlineEmployeeList = realm.where(Employee.class).equalTo("syncInfo.isSync",false).findAllAsync();
RealmList<Student> offlineEmployeeList = realm.where(Student.class).equalTo("syncInfo.isSync",false).findAllAsync();
so what i am looking is , do we have any generalized way to check offline / unsynced data without checking into each entity i.e. Employee and Student.
tomorrow if one more entity got introduced , i again have to fire the same query.
You can create this generic method:
private <T extends RealmModel> RealmResults<T> getNotSynced(Class<T> modelClass) {
return realm.where(modelClass).equalTo("isSynced", false).findAll();
}
And use it like this:
Set<Class<? extends RealmModel>> realmObjectClasses = realm.getConfiguration().getRealmObjectClasses();
ArrayList<RealmResults> notSynched = new ArrayList<>();
for(Class modelClass: realmObjectClasses) {
notSynched.add(getNotSynced(schemaClasses));
}
// Now you have an array of RealmResults with non-synched RealmObjects
About the query method,
I would use the findAll() method and execute the hole thing on a background thread, and not the findAllAsync(), the later requires a more complex synchronisation as I see it.
UPDATE
If not all RealmObjects has the isSynced field, here is what I would do:
RealmObjects has some limitations, but you can implement an empty interface, so I would use that to identify syncable objects and use the getNotSynced method only on them:
Create an empty interface:
public interface Syncable {}
Implement it with all the syncable RealmObjects:
Employee extends RealmObject implements Syncable {
...
}
And then use it to filter the classes in the for loop from before:
for(Class modelClass: realmObjectClasses) {
if (Syncable.class.isAssignableFrom(modelClass)) {
notSynched.add(getNotSynced(schemaClasses));
}
}
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) {
}
});
I have a list view in android where i have to check every time do display the List item or not
to reduce the requests what i did is saved the id in a single row like
1,2,10,
everything was working fine to search i just had to use
String[] favs = fav.split(",");
for (int index = 0; index <(favs.length); index++) {
if(favs[index]==""){}else {
wishlist.add(Integer.parseInt(favs[index].trim()));
}
if(clicklist.contains((int)temp.getId())) //like this
and to remove from db like, this
temp2.replaceAll(""+m1.getId()+",", "") // and save in the db
now issue is i have two more data field associated with id like
10|data1|data2,100|apple|dog,150|data12|data24
Question 1 is this data model ok for small db
Question 2 how to perform search and delete in new data set?
please help!
Using a db is a proper choice here, i suggest you to take a look to the recently released Room, an Android component made by Google developers to support data persistence more easily.
You should of course know the basis of sql language.
In your case you should annotate your data class with #Entity annotation:
#Entity
public class DataModel {
#PrimaryKey
private int uid;
#ColumnInfo(name = "animal")
private String animal;
#ColumnInfo(name = "fruit")
private String fruit;
// Getters and setters are ignored for brevity,
// but they're required for Room to work.
}
And then, to answer your question about CRUD operations, define a Dao:
#Dao
public interface UserDao {
#Query("SELECT * FROM DataModel")
List<DataModel> getAll();
#Insert
void insertAll(DataModel... dataModels);
#Delete
void delete(DataModel dataModel);
}