I want to make a Query to get all birds in a family and I'm not sure how to do it. I've done this so far but I feel like I'm missing something:
DAO:
#Query("Select * from bird WHERE family=:family")
public LiveData<List<Bird>> getBirdsFromFamily(String family);
Repository:
private LiveData<List<Bird>> allBirdsFromFamily;
private String family;
public BirdRepository(Application application){
Database database = Database.getInstance(application);
dao = database.dao();
allBirdsFromFamily = dao.getBirdsFromFamily(family);
}
public LiveData<List<Bird>> getBirdsFromFamily(String family){
return allBirdsFromFamily;
}
View model:
private LiveData<List<Bird>> allBirdsFromFamily;
private String family;
public BirdViewModel(#NonNull Application application) {
super(application);
birdRepository = new BirdRepository(application);
allBirdsFromFamily = birdRepository.getBirdsFromFamily(family);
}
public LiveData<List<Bird>> getAllBirdsFromFamily(String family)
{
return allBirdsFromFamily;
}
I have a list of families and a list of birds.
When clicking on an item of the family list, I want to open a list with all the birds inside. I can get the String out of the item but nothing appears when I call the method.
String family = intent.getStringExtra(AddEditFamilyActivity.EXTRA_FAMILY);
birdViewModel.getAllBirdsFromFamily(family).observe(this, new Observer<List<Bird>>() {
#Override
public void onChanged(#Nullable List<Bird> birds) { //everytime something changes, the adaptater is updated
//update the recycler view
adapter.submitList(birds);
}
});
As my comment is a bit long i'll make a post.
I don't have any doInBackGround for this query, how should I do it ?
Yes, when you query to the database you want to avoid using the Main Thread for 2 reasons, the first one is because you'll get a crash from Android telling you not to do it. And the second is to let your user interact with your application while it do the work of getting the data.
In this doc you can look at the samples and Codelabs and to learn how to make a call to database properly.
I resolved it with a factory - doInBackground wasn't necessary because it's only a "get" query :
DAO :
#Query("Select * from bird WHERE family=:family ORDER BY name")
public LiveData<List<Bird>> getBirdsFromFamily(String family);
Repository :
private LiveData<List<Bird>> allBirdsFromFamily;
private String family;
public BirdRepository(Application application){
Database database = Database.getInstance(application);
birdDao = database.dao();
allBirdsFromFamily = birdDao.getBirdsFromFamily(family);
}
public LiveData<List<Bird>> getAllBirdsFromFamily(String family){
return birdDao.getBirdsFromFamily(family);
}
ViewModel :
private LiveData<List<Bird>> allBirdsFromFamily;
private BirdRepository repository;
public BirdViewModel(#NonNull Application application, String family ) {
super(application);
this.application = application;
repository = new BirdRepository(application);
allBirdsFromFamily = repository.getAllBirdsFromFamily(family);
}
public LiveData<List<Bird>> getAllBirdsFromFamily(String family) { return repository.getAllBirdsFromFamily(family); }
ViewModelFactory :
private Application mApplication;
private String mfamily;
public BirdViewModelFactory(#NonNull Application application, String family) {
mApplication = application;
mfamily = family;
}
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
return (T) new BirdViewModel(mApplication, mfamily);
}
Activity :
BirdViewModelFactory factory = new BirdViewModelFactory(this.getApplication(), family);
birdViewModel = new ViewModelProvider(this, factory).get(BirdViewModel.class);
Related
I'm designing a music information system. I have a couple of entities that are connected to each other.
Below is part of the domain code.
class Album {
private Set<Track> tracks;
private boolean published;
public Set<Track> getTracks() {
return this.tracks;
}
public boolean isPublished() {
return this.published;
}
public void publish() {
System.out.println("Album.publish() called");
this.published = true;
this.tracks.forEach(track -> track.publish());
}
}
class Track {
private boolean published;
private Album album;
public boolean isPublished() {
return this.published;
}
public Album getAlbum() {
return this.album;
}
public void publish() {
// if track is single (this.album == null), set published to true
// if track is part of an album and the album is NOT published, return;
// if track is part of an album and the album is published, set published to true
if(this.album != null && !this.album.isPublished())
return;
this.published = true;
}
}
Track is an independent entity. It can be a single track (I.e. without an Album). So the album attribute is actually needed.
One domain rule is that when an album is archived (i.e. not published), its tracks cannot be published neither and if an album is published, any of its tracks can either be published or archived.
The problem is that when an album is published (e.g. album1.publish()), its tracks' publish() method is called as well. But track1.publish() checks if the album is published based on the copy it already has (which is not published).
How can I solve the problem?
If you split domain model entities by behaviour, you can get rid of described limitations
Let's have some interfaces for such entities:
interface AlbumId{
String asString();
AlbumId Absent = () -> "NO ALBUM AT ALL";
}
interface Publication{
void publish() throws Exception;
void archive() throws Exception;
boolean published();
}
interface Track{
TrackId id();
AlbumId albumId(); //smart type (as DDD suggest), therefore, no more nulls
}
Now you may enforce rules by creating class that will get you a list of tracks you can publish:
public class TracksReadyToPublishOf implements Supplier<Map<TrackId, TrackPublication>>{
//this class may access to cache and have dosens of other optimizations
public TracksReadyToPublishOf(AlbumId id){...}
#Override public get(){...}
}
Then you can reuse your code to check your rules anywhere:
public class TrackPublication implements Publication {
private final Track track;
private final Supplier<Map<TrackId, TrackPublication>> allowedTracks;
//easy for unit testing
public SmartTrackPublication(Track track, Supplier<Map<TrackId, TrackPublication>> allowedTracks){
this.track = track;
this.allowedTracks = allowedTracks;
}
public SmartTrackPublication(Track track){
this(track, new TracksReadyToPublishOf(track.albumId());
}
#Override
public publish() throws AlbumArchivedException{
if(this.albumId != AlbumId.Absent){
if(!this.allowedTracks.get().containsKey(this.track.id())){
throw new AlbumArchivedException();
}
}
this.allowedTracks.get().get(this.id()).publish();
}
}
And for album publishing:
public class AlbumPublication implements Publication{
private final AlbumId id;
private final Producer<Map<TrackId, TrackPublication>> tracks
private AlbumWithTracks(AlbumId id, Producer<Map<TrackId, TrackPublication>> tracks){
this.id = id;
this.tracks = tracks;
}
public AlbumWithTracks(AlbumId id){
this(id, new TracksReadyToPublishOf(id))
}
...
#Override publish() throws Exception{
//code for publishing album
for(TrackPublication t : Arrays.asList(
this.tracks.get()
)){
t.publish(); //track can publish anyway if it presents in list above
}
}
}
I'm using a room database for saving some apps details in a table. And getting live data.
But I'm unable to use conditions.
#Query("SELECT * FROM apps WHERE parent LIKE: parent")
LiveData<List<App>> getAllApp(String parent);
Here is the condition I'm trying to use in DAO. But I can't figure, where should I pass the parameter in REPOSITORY and VIEWNODEL class;
Please help me. May be my question is not that clear but please help me.
public class AppRepository {
private AppDao appDao;
private LiveData<List<App>> appLiveData;
AppRepository(Application application){
Database database = Database.getDatabase(application);
appDao = database.appDao();
appLiveData = appDao.getAllApp();
}
LiveData<List<App>> getAllApp(){
return appLiveData;
}
void insert(App app){
Database.writer.execute(() ->{
appDao.insert(app);
});
}
void delete(App app){
Database.writer.execute(() ->{
appDao.delete(app);
});
}
}
public class AppViewModel extends AndroidViewModel {
private AppRepository repository;
private LiveData<List<App>> appLiveData;
public AppViewModel(#NonNull Application application) {
super(application);
repository = new AppRepository(application);
appLiveData = repository.getAllApp();
}
public LiveData<List<App>> getAllApp( ){
return appLiveData;
}
public void insert(App app){
repository.insert(app);
}
private void delete(App app){
repository.delete(app);
}
}
I want to get the id auto-generated while performing insert operation on Room database. I am implementing MVVM (Model-View-ViewModel) architecture which makes use of DAO to fire queries to Room database. I have added a repository layer between viewmodel and DAO to create an AsyncTask to perform database operations. How do I get the output of insert operation (which is the inserted row's auto-generated id) to the fragment that uses the viewmodel. The layers are as follows: Fragment -> ViewModel -> Repository -> DAO
ListFragment.java
public class ListFragment extends Fragment {
private ReminderViewModel viewModel;
private int id;
...
viewModel = ViewModelProviders.of(this).get(ReminderViewModel.class);
...
id = viewModel.insert(new TodoReminder(0, description, date, time));
...
}
ReminderViewModel.java
public class ReminderViewModel extends AndroidViewModel {
private ReminderRepository repository;
public ReminderViewModel(#NonNull Application application) {
super(application);
repository = new ReminderRepository(application);
}
public int insert(TodoReminder reminder) {
repository.insert(reminder);
}
}
ReminderRepository.java
public class ReminderRepository {
private ReminderDAO reminderDAO;
public ReminderRepository(Application application) {
AppDatabase db = AppDatabase.getDatabase(application);
reminderDAO = db.getReminderDAO();
}
public int insert(TodoReminder reminder) {
new insertAsyncTask(reminderDAO).execute(reminder);
}
private static class InsertAsyncTask extends AsyncTask<TodoReminder, Void, Integer> {
private ReminderDAO asyncTaskDAO;
insertAsyncTask(ReminderDAO dao) {
asyncTaskDAO = dao;
}
#Override
protected Integer doInBackground(final TodoReminder... reminders) {
return asyncTaskDAO.insert(reminders[0]);
}
}
}
ReminderDAO.java
#Dao
public interface ReminderDAO {
#Insert
public int insert(TodoReminder... reminders);
}
ToDoReminder.java
public class TodoReminder implements Serializable {
#PrimaryKey(autoGenerate = true)
#NonNull
private int id;
...
}
How should I get the int returned from the insert method of ReminderDAO and return it from the insert method in ReminderRepository?
You can create your database table in such a way that the id is incremented automatically. In MySQL that is done via the auto_increment keyword. In SQL Server it is done via the identity(1, 1) syntax. In Access it is the autoincrement keyword. In Oracle and PostgreSQL it is done using sequences. If you manage to do this, then you will not need to manually work on incrementing these values. If, for some reason this is out of the question, then you can create a before insert trigger which will load the maximum id and add 1 to it, storing the result in the id. Or, if even that is out of the question, then you can load the last ID, but that has a different syntax in different databases. Or, you can run a query like this:
select max(id) + 1 from yourtable;
but beware possible performance issues and concurrency problems.
I have the exact problem. This is what I did.
I created an interface
public interface Task
{
void processInsert(long id)
}
supply the interface to the insert fcn in the repository
public class Repository {
private Dao myDao;
public void insertMyObject(MyOBject object,Task myInterface ) {
new insertAysncTask(myDao,myInterface).execute(object);
}
private static class insertAsyncTask extends AysncTask<MyObject,Void,Long>{
private Dao mDao;
private Task mTask;
public insertAysncTask(Dao dao, Task task) {
this.mDao = dao;
this.mTask=task;
}
#Override
protected Long doInBackground(MyObject... myObjects) {
return mDao.insertMyObject(myObjects[0]);
}
#Override
protected void onPostExecute(Long aLong) {
super.onPostExecute(aLong);
mTask.processInsert(aLong);
}
}
}
in the DAO class fcn should have return type of Long
public interface MyDao {
#Insert
Long insertMyObject(MyObject object);
have the ViewModel implement the interface
public class MyObjectViewModel extends AndroidViewModel implements Task{
private Repository mRepository;
public MyObjectViewModel(#NonNull Application application) {
super(application);
mRepository = new Repository(application);
}
#Override
public void processInsert(Long id) {
// code for processing the id returned from the db insert
}
public void insertMyObject(MyObject object){
mRepository.insertMyObject(object,this);}
}
in the activity/fragment call the ViewModel insert
mViewModel.insertMyObject(object);
Update
If you want to return the id to the activity or fragment then have the fragment/activity implement the interface instead of the viewmodel
ListFragment extends Fragment implements Task{
.....
#Override
public void processInsert(Long id){
//process id here
}
//call the insert fcn passing in the reference to the fragment
mViewModel.insertMyObject(object,this)
}
Modify the insert fcn in the View Model to accept a reference to the interface
public void insertMyObject(MyObject object, Task myInterface){
mRepository.insertMyObject(object,myInterface);
}
with this approach, the asynctask in the repository insert fcn will hold a reference to the activity/fragment and if the activity/fragment is destroyed before the asynctask finishes this will be a problem. I think it's better to do the processing in the viewmodel if possible. Fragments/activities should only deal with UI concerns not data processing.
Alternative Method
An alternative would be to use a LiveData with an observer.
public class Repository{
private Dao myDao;
private MutableLiveData<Long> dbInsertId = new MutableLiveData<>();
public void insertMyObject(MyObject object){
insertAysnc(object)
}
private void insertAysnc(final MyObject object){
new Thread(new Runnable() {
#Override
public void run() {
Long id=myDao.insertMyObject(object); //call Dao insert fcn
dbInsertId.postValue(id); //set the value of the livedata to the valued returned from the DB insert fcn
}
}).start();
}
public LiveData<Long> getDbInsertedId(){
return dbInsertId;
}
}
In the ViewModel define this fcn
public LiveData<Long> getDbInsertedId(){
return mRepository.getDbInsertedId();// call the repository getId fcn
}
in the onCreate of Activity/Fragment setup an observer on the LiveData
mViewModel= ViewModelProviders.of(this).get(MyViewModel.class);
mViewModel.getDbInsertedId().observe(this, new Observer<Long>() {
#Override
public void onChanged(Long aLong) {
// do something with the long value returned from the database
}
});
I created a room database following this guide from code labs It makes use of a repository to:
A Repository manages query threads and allows you to use multiple backends. In the most common example, the Repository implements the logic for deciding whether to fetch data from a network or use results cached in a local database.
I followed the guide and i'm now able to create the entity's & retrieve the data. I even went further and created another whole entity outside the scope of the guide.
However I can't find many resources that use this MVVM(?) style so am struggling as to really under stand the repository. For now I want to update a field. Just one, as if I am able to manage that the rest should be similar.
I want to update a field called dartshit and I have the dao method created for this:
#Query("UPDATE AtcUserStats SET dartsHit = :amount WHERE userName = :userName")
void UpdateHitAmount(int amount, String userName);
I have one repository which I assumed I use for all entities:
public class UsersRepository {
private UsersDao mUsersDao;
private AtcDao mAtcDao;
private LiveData<List<Users>> mAllUsers;
private LiveData<List<AtcUserStats>> mAllAtc;
private AtcUserStats mAtcUser;
UsersRepository(Application application) {
AppDatabase db = AppDatabase.getDatabase(application);
mUsersDao = db.usersDao();
mAtcDao = db.atcDao();
mAllUsers = mUsersDao.fetchAllUsers();
mAllAtc = mAtcDao.getAllAtcStats();
}
LiveData<List<Users>> getAllUsers() {
return mAllUsers;
}
LiveData<List<AtcUserStats>> getAllAtcStats() {
return mAllAtc;
}
LiveData<AtcUserStats> getAtcUser(String username) {
return mAtcDao.findByName(username);
}
public void insert (Users user) {
new insertAsyncTask(mUsersDao).execute(user);
}
public void insertAtc (AtcUserStats atc) {
new insertAsyncAtcTask(mAtcDao).execute(atc);
}
private static class insertAsyncTask extends AsyncTask<Users, Void, Void> {
private UsersDao mAsyncTaskDao;
insertAsyncTask(UsersDao dao) {
mAsyncTaskDao = dao;
}
#Override
protected Void doInBackground(final Users... params) {
mAsyncTaskDao.insertNewUser(params[0]);
return null;
}
}
private static class insertAsyncAtcTask extends AsyncTask<AtcUserStats, Void, Void> {
private AtcDao mAsyncTaskDao;
insertAsyncAtcTask(AtcDao dao) {
mAsyncTaskDao = dao;
}
#Override
protected Void doInBackground(final AtcUserStats... params) {
mAsyncTaskDao.insertNewAtcUser(params[0]);
return null;
}
}
}
My question is how do I create a AsyncTask for the update query I am trying to run in this repository?
Here is what I have so far by broadly copying the insert repository methods:
private class updateHitAsyncTask {
private AtcDao mAsyncTaskDao;
public updateHitAsyncTask(AtcDao mAtcDao) {
mAsyncTaskDao = mAtcDao;
}
protected Void doInBackground(int amount, String name) {
mAsyncTaskDao.UpdateHitAmount(amount, name);
return null;
}
}
Which is incorrect is that I'm getting a llegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time. error. But i thought this AsyncTask is suppose to take care of this?
Here is my update method in my view model, which is reporting 0 errors:
void updateHitAmount (int amount, String name) {
mRepository.updateAtcHits(amount, name);
}
and here is the UI code where im actually trying to tie all these together, I suspect there must be a better way that using onChanged for simply updating a field but again I am struggling to come across any advice on google with the repository approach:
private void callOnChanged() {
mAtcViewModel = ViewModelProviders.of(this).get(AtcViewModel.class);
mAtcViewModel.getAllUsers().observe(this, new Observer<List<AtcUserStats>>() {
#Override
public void onChanged(#Nullable final List<AtcUserStats> atc) {
// Update the cached copy of the users in the adapter.
for (int i = 0; i < atc.size(); i++) {
if (atc.get(i).getUserName().equals(mUser)) {
mAtcViewModel.updateHitAmount(55, mUser);
//atc.get(i).setDartsHit(55);
Log.d("id", String.valueOf(userSelected.getId()));
}
}
}
});
How can I update fields using this approach on the background thread?
Figured it out due to this answer here. It was mostly because of my lack of understanding of AsyncTask. Essentially I needed to create an object and pass the data that way and then execute in the background:
private static class MyTaskParams {
int amount;
String name;
MyTaskParams(int amount, String name) {
this.amount = amount;
this.name = name;
}
}
public void updateAtcHits (int amount, String name) {
MyTaskParams params = new MyTaskParams(amount,name);
new updateHitAsyncTask(mAtcDao).execute(params);
}
private class updateHitAsyncTask extends AsyncTask<MyTaskParams,Void,Void>{
private AtcDao mAsyncTaskDao;
public updateHitAsyncTask(AtcDao mAtcDao) {
mAsyncTaskDao = mAtcDao;
}
#Override
protected Void doInBackground(MyTaskParams... myTaskParams) {
int amount =myTaskParams[0].amount;
String name = myTaskParams[0].name;
mAsyncTaskDao.UpdateHitAmount(amount, name);
return null;
}
}
I've been using the codelabs for my Room Database Persistence. Now I am trying to get the latest rowID after inserting data into my room database. However, I am stuck in my repository trying to return the rowID from my AsyncTask.
LogEntity.java
#Entity
public class LogEntity {
#PrimaryKey(autoGenerate = true)
private int id;
LogDao.java
public interface LogDao {
#Insert
long insert(LogEntity logEntity);
LogDatabase.java
#Database(entities = LogEntity.class, version = 1)
public abstract class LogDatabase extends RoomDatabase {
private static LogDatabase instance;
public abstract LogDao logDao();
public static synchronized LogDatabase getInstance(Context context){
if (instance == null){
instance = Room.databaseBuilder(context.getApplicationContext(),
LogDatabase.class, "log_database").
fallbackToDestructiveMigration().build();
}
return instance;
}
}
LogRepository.java
public long insertLogs(LogEntity logEntity) {
new InsertLogAsyncTask(logDao).execute(logEntity);
return **
}
private static class InsertLogAsyncTask extends AsyncTask<LogEntity, Void, Long>{
private LogDao logDao;
private InsertLogAsyncTask(LogDao logDao){
this.logDao = logDao;
}
#Override
protected Long doInBackground(LogEntity... logEntities) {
logDao.insert(logEntities[0]);
return logDao.insert(logEntities[0]);
}
}
I put two asterisks because I'm not sure what to do here in order to get the insert RowID and whether my AsyncTask is completely correct.
LogViewModel.java
public long insertLog(LogEntity logEntity){
return repository.insertLogs(logEntity);
}
MainActivity.java
long id = logViewModel.insertLog(logEntity);
I want to be able to use this final id variable for future use.
You are on the right track but not quite there.
You should declare the AsyncTask class as an inner class of the ViewModel and not the DB.
In the ViewModel add an ID variable and in the AsyncTask add the onPostExecute override to handle the execution result.
LogViewModel.java
long mLastInsertedID;
private static class InsertLogAsyncTask extends AsyncTask<LogEntity, Void, Long>{
private LogDao logDao;
private InsertLogAsyncTask(LogDao logDao){
this.logDao = logDao;
}
#Override
protected Long doInBackground(LogEntity... logEntities) {
//you are now off the UI thread
logDao.insert(logEntities[0]);
return logDao.insert(logEntities[0]);
}
#Override
protected void onPostExecute(Long result) {
//Do whatever you like with the result as you are back on the UI thread
mLastInsertedID = result;
}
}