Populate Spinner from LiveData (Room Database) - android

Where I am
I am attempting to fill a spinner with data from a database, using Room. The data is a list of terms, which have courses associated with them.
I want to use the spinner when creating a new course, to select a term to associate it with.
Currently, the spinner does not show a default option, but if you click the spinner it shows a list of the data to select from. After you select something from the spinner, it does not show what you have selected.
Here is my code for loading data into the spinner's adapter:
termsList = new ArrayList<>();
termIdList = new ArrayList<>();
mTermViewModel = new ViewModelProvider(this).get(TermViewModel.class);
mTermViewModel.getAllTerms().observe(this, new Observer<List<TermEntity>>() {
#Override
public void onChanged(#Nullable final List<TermEntity> terms) {
for (TermEntity term : terms) {
termsList.add(term.getTermName());
termIdList.add(term.getTermId());
}
}
});
ArrayAdapter<String> adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, termsList);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mTermSpinner.setAdapter(adapter);
}
Here is the TermDAO
#Dao
public interface TermDAO {
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(TermEntity term);
#Query("DELETE FROM terms")
void deleteAllTerms();
#Query("SELECT * FROM terms ORDER BY termId ASC")
LiveData<List<TermEntity>> getAllTerms();
#Query("SELECT * FROM terms WHERE termId = :termId")
LiveData<List<TermEntity>> getTerm(int termId);
#Query("SELECT termId FROM terms WHERE termName = :termName")
LiveData<List<TermEntity>> getTermIdByTermName(String termName);
}
Here is the TermViewModel
public class TermViewModel extends AndroidViewModel {
private SchoolTrackerRepository mRepository;
private LiveData<List<TermEntity>> mAllTerms;
public TermViewModel(#NonNull Application application) {
super(application);
mRepository = new SchoolTrackerRepository(application);
mAllTerms = mRepository.getAllTerms();
}
public LiveData<List<TermEntity>> getAllTerms() {
return mAllTerms;
}
public void insert(TermEntity termEntity) {
mRepository.insert(termEntity);
}
}
I want to show the term name in the spinner and use the corresponding termId to perform the query.
What I've tried
I have tried using Mutable LiveData instead of LiveData but when I attempted to run it, I got an error that said:
Error:Not sure how to convert a Cursor to this method's return type
I'm really at a loss. Thanks for any help.
Added Repository
public class SchoolTrackerRepository {
private TermDAO mTermDAO;
private CourseDAO mCourseDAO;
int termId;
private LiveData<List<TermEntity>> mAllTerms;
private LiveData<List<CourseEntity>> mAllCourses;
private LiveData<List<CourseEntity>> mAssociatedCourses;
public SchoolTrackerRepository(Application application) {
SchoolTrackerDatabase db = SchoolTrackerDatabase.getDatabase(application);
mTermDAO = db.termDAO();
mCourseDAO = db.courseDAO();
mAllTerms = mTermDAO.getAllTerms();
mAllCourses = mCourseDAO.getAllCourses();
}
public LiveData<List<TermEntity>> getAllTerms() {
return mAllTerms;
}
public LiveData<List<CourseEntity>> getAllCourses() {
return mAllCourses;
}
public LiveData<List<CourseEntity>> getmAssociatedCourses(int termId) {
return mAssociatedCourses;
}
public void insert(TermEntity termEntity) {
new insertAsyncTask1(mTermDAO).execute(termEntity);
}
private static class insertAsyncTask1 extends AsyncTask<TermEntity, Void, Void> {
private TermDAO mAsyncTaskDAO;
insertAsyncTask1(TermDAO dao) {
mAsyncTaskDAO = dao;
}
#Override
protected Void doInBackground(final TermEntity... params) {
mAsyncTaskDAO.insert(params[0]);
return null;
}
}
public void insert(CourseEntity courseEntity) {
new insertAsyncTask2(mCourseDAO).execute(courseEntity);
}
private static class insertAsyncTask2 extends AsyncTask<CourseEntity, Void, Void> {
private CourseDAO mAsyncCourseDAO;
insertAsyncTask2(CourseDAO dao) {
mAsyncCourseDAO = dao;
}
#Override
protected Void doInBackground(final CourseEntity... params) {
mAsyncCourseDAO.insert(params[0]);
return null;
}
}
}

I guess that you missing notifyDataSetChanged after update the termsList variable.
Take a try with this:
ArrayAdapter<String> adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, termsList);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mTermSpinner.setAdapter(adapter);
mTermViewModel = new ViewModelProvider(this).get(TermViewModel.class);
mTermViewModel.getAllTerms().observe(this, new Observer<List<TermEntity>>() {
#Override
public void onChanged(#Nullable final List<TermEntity> terms) {
for (TermEntity term : terms) {
termsList.add(term.getTermName());
termIdList.add(term.getTermId());
}
//notifyDataSetChanged after update termsList variable here
adapter.notifyDataSetChanged();
}
});

Related

Android Fragment LiveData observer is not triggered when update is done on a record data

I am trying to figure out why the LiveData observer for getAllGoals() does not trigger immediately in the fragment when I update a record. However, the observer is called only after switching to another fragment using the bottom tab navigation and then coming back to the original fragment.
The fragment in question:
MyGoalsFragment.java
public class MyGoalsFragment extends Fragment implements MyGoalsAdapter.MyGoalsCallback {
FragmentMyGoalsBinding myGoalsBinding;
private MyGoalsViewModel myGoalsViewModel;
MyGoalsAdapter myGoalsAdapter;
ConstraintSet smallConstraintSet = new ConstraintSet();
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
myGoalsViewModel = new ViewModelProvider(getActivity(), ViewModelProvider.AndroidViewModelFactory.getInstance(getActivity().getApplication())).get(MyGoalsViewModel.class);
myGoalsBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_my_goals, container, false);
myGoalsBinding.recyclerView2.setLayoutManager(new LinearLayoutManager(getActivity()));
DrawerLayout drawerLayout = (DrawerLayout) getActivity().findViewById(R.id.drawer_layout);
myGoalsBinding.menu.setOnClickListener(v -> {
drawerLayout.openDrawer(GravityCompat.START);
});
TransitionManager.beginDelayedTransition(myGoalsBinding.recyclerView2);
myGoalsAdapter = new MyGoalsAdapter();
myGoalsAdapter.setCallback(this);
myGoalsAdapter.setContext(getActivity());
myGoalsAdapter.setRecyclerView(myGoalsBinding.recyclerView2);
myGoalsBinding.recyclerView2.setAdapter(myGoalsAdapter);
myGoalsBinding.floatingActionButton.setOnClickListener(v -> {
startActivity(new Intent(getActivity(), CreateGoalActivity.class));
getActivity().finish();
});
enableSwipeToDeleteAndUndo();
myGoalsBinding.recyclerView2.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0 && myGoalsBinding.floatingActionButton.getVisibility() == View.VISIBLE) {
myGoalsBinding.floatingActionButton.hide();
} else if (dy < 0 && myGoalsBinding.floatingActionButton.getVisibility() != View.VISIBLE) {
myGoalsBinding.floatingActionButton.show();
}
}
});
myGoalsViewModel.getAllGoals().observe(getViewLifecycleOwner(), new Observer<List<Goal>>() {
#Override
public void onChanged(List<Goal> goals) {
myGoalsAdapter.submitList(goals); // This observer is not called even after updating a record
}
});
return myGoalsBinding.getRoot();
}
#Override
public void editGoalCallback(Goal goal) {
Intent intent = new Intent(getActivity(), CreateGoalActivity.class);
Bundle bundle = new Bundle();
bundle.putSerializable("goal", goal);
intent.putExtras(bundle);
startActivity(intent);
}
#Override
public void goalCheckBoxCallback(Goal goal) {
myGoalsViewModel.updateGoal(goal);
}
private void enableSwipeToDeleteAndUndo() {
SwipeToDeleteCallback swipeToDeleteCallback = new SwipeToDeleteCallback(getActivity()) {
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int i) {
if(i==ItemTouchHelper.LEFT) {
Goal tempGoal = myGoalsAdapter.getGoalAt(viewHolder.getAdapterPosition());
myGoalsViewModel.deleteGoal(tempGoal);
Snackbar.make(myGoalsBinding.rootConstraintLayout, "Goal Deleted", Snackbar.LENGTH_LONG)
.setAction("Undo", v -> {
myGoalsViewModel.insertGoal(tempGoal);
})
.setActionTextColor(getActivity().getResources().getColor(R.color.arcticLimeGreen))
.show();
}else if(i==ItemTouchHelper.RIGHT){
Goal tempGoal = myGoalsAdapter.getGoalAt(viewHolder.getAdapterPosition());
if(tempGoal.isCompleted())
tempGoal.setCompleted(false);
else
tempGoal.setCompleted(true);
TransitionManager.beginDelayedTransition(myGoalsBinding.recyclerView2);
myGoalsViewModel.updateGoal(tempGoal); // This is where the update is called
}
}
};
ItemTouchHelper itemTouchhelper = new ItemTouchHelper(swipeToDeleteCallback);
itemTouchhelper.attachToRecyclerView(myGoalsBinding.recyclerView2);
}
}
The MyGoals ViewModel:
public class MyGoalsViewModel extends AndroidViewModel {
private NoteRepository repository;
private LiveData<List<Goal>> allGoals;
public MyGoalsViewModel(#NonNull Application application) {
super(application);
repository = new NoteRepository(application);
allGoals = repository.getAllGoals();
}
public LiveData<List<Goal>> getAllGoals(){
return allGoals;
}
public void deleteGoal(Goal goal){repository.deleteGoal(goal);}
public void insertGoal(Goal goal){repository.insertGoal(goal);}
public void updateGoal(Goal goal){repository.updateGoal(goal);}
}
The Repository:
public class NoteRepository {
private String DB_NAME = "db_task";
Context context;
private GoalDao goalDao;
private LiveData<List<Goal>> allGoals;
private NoteDatabase noteDatabase;
public NoteRepository(Context context) {
noteDatabase = NoteDatabase.getInstance(context);
goalDao = noteDatabase.goalDao();
allGoals = goalDao.getAllGoals();
this.context = context;
}
public void insertGoal(Goal goal){
new InsertGoalAsyncTask(goalDao).execute(goal);
}
public void deleteGoal(Goal goal){
new DeleteGoalAsyncTask(goalDao).execute(goal);
}
public void updateGoal(Goal goal){
new UpdateGoalAsyncTask(goalDao).execute(goal);
}
public void deleteAllGoals(){
new DeleteAllGoalAsyncTask(goalDao).execute();
}
public LiveData<List<Goal>> getAllGoals(){
return allGoals;
}
private static class InsertGoalAsyncTask extends AsyncTask<Goal,Void,Void>{
private GoalDao goalDao;
private InsertGoalAsyncTask(GoalDao goalDao){
this.goalDao = goalDao;
}
#Override
protected Void doInBackground(Goal... goals) {
goalDao.insert(goals[0]);
return null;
}
}
private static class DeleteGoalAsyncTask extends AsyncTask<Goal,Void,Void>{
private GoalDao goalDao;
private DeleteGoalAsyncTask(GoalDao goalDao){
this.goalDao = goalDao;
}
#Override
protected Void doInBackground(Goal... goals) {
goalDao.delete(goals[0]);
return null;
}
}
private static class UpdateGoalAsyncTask extends AsyncTask<Goal,Void,Void>{
private GoalDao goalDao;
private UpdateGoalAsyncTask(GoalDao goalDao){
this.goalDao = goalDao;
}
#Override
protected Void doInBackground(Goal... goals) {
goalDao.update(goals[0]);
return null;
}
}
private static class DeleteAllGoalAsyncTask extends AsyncTask<Void,Void,Void>{
private GoalDao goalDao;
private DeleteAllGoalAsyncTask(GoalDao goalDao){
this.goalDao = goalDao;
}
#Override
protected Void doInBackground(Void... voids) {
goalDao.deleteAllGoals();
return null;
}
}
}
The DAO class:
#Dao
public interface GoalDao {
#Insert
void insert(Goal goal);
#Update
void update(Goal goal);
#Delete
void delete(Goal goal);
#Query("DELETE from goal_table")
void deleteAllGoals();
#Query("Select * from goal_table order by end_date")
LiveData<List<Goal>> getAllGoals();
}
I have this issue in 2 fragments and there are 2 other fragments that do not have this issue with the exact same implementation. Why is the observer not being called as soon as I update a record in MyGoals fragment?
I found the solution, the problem was not in the LiveData code, but in the Recyclerview ListAdapter & DiffUtil Implementation which stopped from triggering LiveData change.
In MyGoalsAdapter I have used DiffUtil & ListAdapter to have smooth animations and increase performance. For it to work properly we need to compare the new list with the old list. The Problem is where the contents of an object were being marked as equal when they were actually different. I solved this by adding a date field in my Model class modifiedAt
and updated the field before that Object was updated. Here is the snippet of code to explain it better.
MyGoalsAdapter:
public class MyGoalsAdapter extends ListAdapter<Goal, MyGoalsAdapter.MyGoalsViewHolder> {
private Context context;
public MyGoalsAdapter() {
super(DIFF_CALLBACK);
}
private static final DiffUtil.ItemCallback<Goal> DIFF_CALLBACK = new DiffUtil.ItemCallback<Goal>() {
#Override
public boolean areItemsTheSame(#NonNull Goal oldItem, #NonNull Goal newItem) {
return oldItem.getId() == newItem.getId();
}
#Override
public boolean areContentsTheSame(#NonNull Goal oldItem, #NonNull Goal newItem) { //Here we check if the objects in the list have changed fields.
boolean id,desc,iscomp,edate,etime,sdate,stime,title, naya, purana, createdAt, modifiedAt;
id = oldItem.getId() == newItem.getId();
desc = oldItem.getDescription().equals(newItem.getDescription());
purana = oldItem.isCompleted();
naya = newItem.isCompleted();
iscomp = purana && naya;
edate = oldItem.getEnd_date().equals(newItem.getEnd_date());
etime = oldItem.getEnd_time().equals(newItem.getEnd_time());
sdate = oldItem.getStart_date().equals(newItem.getStart_date());
stime = oldItem.getStart_time().equals(newItem.getStart_time());
title = oldItem.getTitle().equals(newItem.getTitle());
createdAt = oldItem.getCreatedAt().equals(newItem.getCreatedAt());
modifiedAt = oldItem.getModifiedAt().equals(newItem.getModifiedAt()); //This will return false for the object that is changed
return id &&
desc &&
iscomp &&
edate &&
etime &&
sdate &&
stime &&
title &&
createdAt &&
modifiedAt
;
}
};
}
When I am updating I set the Object modifiedAt field with the current Date and Time.
Goal tempGoal = myGoalsAdapter.getGoalAt(viewHolder.getAdapterPosition()); //Get the object to make change to it
//make change to the object's field
tempGoal.setModifiedAt(Calendar.getInstance().getTime()); //set the modified date with Current date
myGoalsViewModel.updateGoal(tempGoal); //Update the object to the database
Changing the modifiedAt field will tell the Adapter when there is an object that is updated, triggering the animation and showing the updated object in the List, instantly.
I hope this will help someone.

Populate Room Database with Arraylist

I would like to build a Deckbuilder that allows you to save created decks locally on the device.
The Decklist are stored in Arraylists, called TransferDeck. Which I would like to store in room database. My issue is, that I do not know how to populate my database correctly, with the data comming out of the Arraylist.
I am used to working with Arraylist and below you see my try for storing the data:
So this is what I tried but what sadly does not work:
private void populateDB(final List<TransferDeck> mTransferDeck) {
new Thread(new Runnable() {
#Override
public void run() {
List<SaveDeck> mSaveDeck = new ArrayList<>();
for(int i = 0; i<mTransferDeck.size(); i++){
mSaveDeck.add(new SaveDeck(i, "FirstSavedDeck", mTransferDeck.get(i).getCardImage() ,mTransferDeck.get(i).getTypeImage(), mTransferDeck.get(i).getCost(), mTransferDeck.get(i).getName(), mTransferDeck.get(i).getNumber()));
}
mSavedDecksDB.deckBuilderDao().insertCards(mSaveDeck);
}
}).start();
}
Below you can find the rest of my code, but the above one should be enough to make clear what I want to do...
I created the class SaveDeck which should be able to Save a Deck with a given Deckname:
:-
#Entity
public class SaveDeck implements Serializable {
#PrimaryKey(autoGenerate = true)
private int _id;
public SaveDeck(int _id, String deckName, int cardImage, int typeImage, Integer cardCost, String cardName, Integer cardNumber) {
this._id = _id;
DeckName = deckName;
CardImage = cardImage;
TypeImage = typeImage;
CardCost = cardCost;
CardName = cardName;
CardNumber = cardNumber;
}
#ColumnInfo(name = "DeckName")
private String DeckName;
#ColumnInfo(name = "CardImage")
private int CardImage;
#ColumnInfo(name = "TypeImage")
private int TypeImage;
#ColumnInfo(name = "CardCost")
private Integer CardCost;
#ColumnInfo(name = "CardName")
private String CardName;
#ColumnInfo(name = "CardNumber")
private Integer CardNumber;
}
I created the Dao Class as follows:
:-
#Dao
public interface DeckBuilderDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
public long[] insertCards(SaveDeck... saveDecks);
#Insert(onConflict = OnConflictStrategy.IGNORE)
public long insertCard(SaveDeck saveDecks);
#Update
public int updateCardBaseEntries(SaveDeck... saveDecks);
#Update
public int updateCardBaseEntry(SaveDeck saveDecks);
#Delete
public int deleteCardBaseEntried(SaveDeck... saveDecks);
#Delete
public int deleteCardBaseEntry(SaveDeck saveDecks);
#Query("SELECT * FROM SaveDeck")
public SaveDeck[] getAllDecks();
//probably I do not need the getAllDecks Query. Right now I only need the following one:
#Query("SELECT * FROM SaveDeck WHERE DeckName = :NameOfDeck ORDER BY DeckName, CardName")
public SaveDeck getOneDeck(String NameOfDeck);
}
Furthermore created the DataBase Class:
#Database(entities = {SaveDeck.class}, version = 1)
public abstract class SaveDecksDataBase extends RoomDatabase {
public abstract DeckBuilderDao deckBuilderDao();
}
The last class is a fragment, where I try to populate my database, and in the populateDB() class is the issue
public class review_fragment extends Fragment {
private List<TransferDeck> mTransferDeck = DataHolder.getInstance().savedDecklistTransfer;
SaveDecksDataBase mSavedDecksDB;
Cursor mCursor;
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
//return super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.review_fragment, container, false);
/*Introduce Cards Recycler*/
RecyclerView rvCards = view.findViewById(R.id.rv_review_cardlist);
rvCards.setLayoutManager(new GridLayoutManager(getActivity(), 5));
review_RViewAdapter_Cards adapterCards = new review_RViewAdapter_Cards(getContext(), mTransferDeck);
rvCards.setAdapter(adapterCards);
/*Init Room database*/
mSavedDecksDB = Room.databaseBuilder(getActivity(),SaveDecksDataBase.class,"SavedDecksDB.db").build();
populateDB(mTransferDeck);
return view;
}
private void populateDB(final List<TransferDeck> mTransferDeck) {
new Thread(new Runnable() {
#Override
public void run() {
List<SaveDeck> mSaveDeck = new ArrayList<>();
for(int i = 0; i<mTransferDeck.size(); i++){
mSaveDeck.add(new SaveDeck(i, "FirstSavedDeck", mTransferDeck.get(i).getCardImage() ,mTransferDeck.get(i).getTypeImage(), mTransferDeck.get(i).getCost(), mTransferDeck.get(i).getName(), mTransferDeck.get(i).getNumber()));
}
mSavedDecksDB.deckBuilderDao().insertCards(mSaveDeck);
}
}).start();
}
}
I like to mention that this should be a comment rather than an answer.
First, either use AysncTask or use more robust Executors.newSingleThreadExecutor(). If you prefer the second one then it's best if you create a helper class (example). Example:
private void populateDB(final List<TransferDeck> mTransferDeck) {
AppExecutors.diskIO().execute(() -> {
for(int i = 0; i<mTransferDeck.size(); i++){
mSavedDecksDB.deckBuilderDao().insertCards(new SaveDeck(...);
}
});
}
(1) Create a blank constructor.
(4) Room Database should not be initialized there and it's best if it's singleton. So the your database class (3) can be like:
public abstract class SaveDecksDataBase extends RoomDatabase {
private static SaveDecksDataBase sINSTANCE;
private static final Object LOCK = new Object();
public static SaveDecksDataBase getDatabase(final Context context) {
if (sINSTANCE == null) {
synchronized (LOCK) {
if (sINSTANCE == null) {
sINSTANCE = Room.databaseBuilder(context.getApplicationContext(),
SaveDecksDataBase.class, "SavedDecksDB.db")
.build();
}
}
}
return sINSTANCE;
}
public abstract DeckBuilderDao deckBuilderDao();
}
Lastly, to get SaveDeck object you also has to use Executors or AsyncTask to do the work in background, and then populate the RecyclerView.
Android Room Database
Practice set

How to show data from SQLite with CRUD in Android Studio?

Hi I'm doing an app and I made the database but I don't know how to read it to show in a listview for example I have a table CategoriaEntradas and I need to show all the data in a listview but I don't understand very well how it works all the classes
This is my Dao class
#Dao
public interface CategoriaEntradasDao {
#Insert
void insert(CategoriaEntradas categoriaEntradas);
#Update
void update(CategoriaEntradas categoriaEntradas);
#Delete
void delete(CategoriaEntradas categoriaEntradas);
#Query("DELETE FROM CategoriaEntradas")
void deleteAll();
#Query("SELECT * FROM CategoriaEntradas")
LiveData<List<CategoriaEntradas>> getAll();
#Query("SELECT * FROM CategoriaEntradas WHERE IdCategoria =:Id")
LiveData<CategoriaEntradas> getOne(int Id);
}
This is my Entity class
#Entity(tableName = "CategoriaEntradas")
public class CategoriaEntradas {
#PrimaryKey(autoGenerate = true)
#NonNull
#ColumnInfo(name = "IdCategoria")
private Integer _idCategoria;
#NonNull
#ColumnInfo(name = "Name")
private String _name;
#NonNull
#ColumnInfo(name = "Image")
private String _image;
public CategoriaEntradas(#NonNull String name, #NonNull String image) { _name = name; _image = image;}
public void set_idCategoria(Integer _idCategoria){
this._idCategoria = _idCategoria;
}
#NonNull
public Integer getIdCategoria() {
return _idCategoria;
}
#NonNull
public String getName() {
return _name;
}
#NonNull
public String getImage() {
return _image;
}
}
My Repository class
public class CategoriaEntradasRepository {
private CategoriaEntradasDao categoriaEntradasDao;
private LiveData<List<CategoriaEntradas>> listLiveData;
public CategoriaEntradasRepository(Application application) {
Database db = Database.getDatabase(application);
categoriaEntradasDao = db.categoriaEntradasDao();
listLiveData = categoriaEntradasDao.getAll();
}
public LiveData<List<CategoriaEntradas>> getAll() {
return listLiveData;
}
public LiveData<CategoriaEntradas> getOne(int Id) {
return categoriaEntradasDao.getOne(Id);
}
public void insert (CategoriaEntradas categoriaEntradas) {
new CategoriaEntradasRepository.insertAsyncTask(categoriaEntradasDao).execute(categoriaEntradas);
}
private static class insertAsyncTask extends AsyncTask<CategoriaEntradas, Void, Void> {
private CategoriaEntradasDao entradasDao;
insertAsyncTask(CategoriaEntradasDao dao) {
entradasDao = dao;
}
#Override
protected Void doInBackground(final CategoriaEntradas... params) {
entradasDao.insert(params[0]);
return null;
}
}
public void update(final CategoriaEntradas categoriaEntradas){
new CategoriaEntradasRepository.updateAsyncTask(categoriaEntradasDao).execute(categoriaEntradas);
}
private static class updateAsyncTask extends AsyncTask<CategoriaEntradas, Void, Void>{
private CategoriaEntradasDao entradasDao;
updateAsyncTask(CategoriaEntradasDao dao) {
entradasDao = dao;
}
#Override
protected Void doInBackground(final CategoriaEntradas... params){
entradasDao.update(params[0]);
return null;
}
}
public void delete(final CategoriaEntradas categoriaEntradas) {
new CategoriaEntradasRepository.deleteAsyncTask(categoriaEntradasDao).execute(categoriaEntradas);
}
public void delete(final int Id) {
final LiveData<CategoriaEntradas> categoriaEntradas = getOne(Id);
if (categoriaEntradas != null) {
new CategoriaEntradasRepository.deleteAsyncTask(categoriaEntradasDao).execute(categoriaEntradas.getValue());
}
}
private static class deleteAsyncTask extends AsyncTask<CategoriaEntradas, Void, Void>{
private CategoriaEntradasDao entradasDao;
deleteAsyncTask(CategoriaEntradasDao dao) {
entradasDao = dao;
}
#Override
protected Void doInBackground(final CategoriaEntradas... params){
entradasDao.delete(params[0]);
return null;
}
}
}
And my ViewModel class
public class CategoriaEntradasViewModel extends AndroidViewModel {
private CategoriaEntradasRepository repository;
private LiveData<List<CategoriaEntradas>> listLiveData;
public CategoriaEntradasViewModel (Application application) {
super(application);
repository = new CategoriaEntradasRepository(application);
listLiveData = repository.getAll();
}
public LiveData<List<CategoriaEntradas>> getAll() { return listLiveData; }
public LiveData<CategoriaEntradas> getOne(int Id) { return repository.getOne(Id); }
public void insert(CategoriaEntradas categoriaEntradas) { repository.insert(categoriaEntradas); }
public void update(CategoriaEntradas categoriaEntradas){ repository.update(categoriaEntradas);}
public void delete(CategoriaEntradas categoriaEntradas) {repository.delete(categoriaEntradas);}
public void delete(int Id) {repository.delete(Id);}
}
Try this at your Activity or fragment that have your listview.
CategoriaEntradasViewModel mViewModel = ViewModelProviders.of(this).get(CategoriaEntradasViewModel.class);
mViewModel.getAll().observe(this, new Observer<List<CategoriaEntradas>>() {
#Override
public void onChanged(List<CategoriaEntradas> categoriaEntradas) {
adapter = new YourListViewAdapter(this, categoriaEntradas);
listview.setAdapter(adapter);
}
}
Yes, if you have custom list item view. Its similar to array adapter.

Room Update not Updating Database

I'm new to Room and am having some trouble updating the database. Insert and delete work as expected but the update does not. The data remains the same in the database. I've tried the update using the entire object and the specific field in an #Query statement. What am I doing wrong?
Entity:
#Entity(tableName = "Appointments")
public class Appointment {
#PrimaryKey
#NonNull
public int AppointmentID;
public Boolean CheckInStatus;
public Appointment(
#NonNull int AppointmentID,
Boolean CheckInStatus
) {
this.AppointmentID = AppointmentID;
this.CheckInStatus = CheckInStatus;
}
public void setCheckInStatus(Boolean checkInStatus) {
CheckInStatus = checkInStatus;
}
#NonNull
public int getAppointmentID() {
return this.AppointmentID;
}
public Boolean getCheckInStatus() {
return CheckInStatus;
}
public void setID(#NonNull int AppointmentID) {
this.AppointmentID = AppointmentID;
}
Dao:
#Update
void update(Appointment appointment);
#Query("UPDATE Appointments SET CheckInStatus = 1 WHERE AppointmentID = :Id")
void updateCheckIn(int Id);
Repo:
public void updateCheckIn(int Id) {
new AppointmentRepository.updateCheckInAsyncTask(mAppointmentDao, Id);
}
public static class updateCheckInAsyncTask extends AsyncTask<Void, Void, Void> {
private AppointmentDao mAsyncTaskDao;
private int mId;
updateCheckInAsyncTask(AppointmentDao dao, int Id) {
mAsyncTaskDao = dao;
mId = Id;
}
#Override
protected Void doInBackground(Void... voids) {
mAsyncTaskDao.updateCheckIn(mId);
return null;
}
}
public void update(Appointment appointment) {
new AppointmentRepository.updateAsyncTask(mAppointmentDao, appointment);
}
private static class updateAsyncTask extends AsyncTask<Void, Void, Void> {
private AppointmentDao mAsyncTaskDao;
private Appointment mAppointment;
updateAsyncTask(AppointmentDao dao, Appointment appointment) {
mAsyncTaskDao = dao;
mAppointment = appointment;
}
#Override
protected Void doInBackground(Void... voids) {
mAsyncTaskDao.update(mAppointment);
return null;
}
}
View Model:
public void update(Appointment appointment) { mRepository.update(appointment); }
public void updateCheckIn(int Id) { mRepository.updateCheckIn(Id); }
Activity:
// Update check in status in table
mAppointmentVM.updateCheckIn(mId);
The correct id is being passed as was the updated object when I went that route. Any assistance is greatly appreciated.
EDIT:
The final answer is:
public void updateCheckIn(int Id) {
new AppointmentRepository.updateCheckInAsyncTask(mAppointmentDao, Id).execute();
}
public static class updateCheckInAsyncTask extends AsyncTask<Void, Void, Void> {
private AppointmentDao mAsyncTaskDao;
private int mId;
updateCheckInAsyncTask(AppointmentDao dao, int Id) {
mAsyncTaskDao = dao;
mId = Id;
}
#Override
protected Void doInBackground(Void... voids) {
Appointment entity = mAsyncTaskDao.getAppointmentDetails(mId);
entity.setCheckInStatus(true);
mAsyncTaskDao.update(entity);
return null;
}
}
You could simply do it like that:
Get item that you want to update from DB;
Update that POJO by setting CheckInStatus with setter method;
Simply use #Update(onConflict = OnConflictStrategy.REPLACE) function and pass your updated POJO to it (because Id's match and its a Primary key, DB will automatically identify this situation and update all required fields for required object);
**Example:**
#Override
protected Void doInBackground(Void... voids) {
Appointment entity = mAsyncTaskDao.getAppointmentById(mId);
entity.setCheckInStatus(true);
mAsyncTaskDao.update(entity);
return null;
}
That is it :) Good luck.

LiveData is not updating its value after first call

I have been beating my head against the wall and I cannot understand why this is happening. I am working with the new Architectural Components for Android and I am having problems updating a LiveData with a List of Objects.
I have two spinners. When i change the option in the first one, The second one must have its content changed. But this last part is not happening.
Can anyone help me?
State.java
#Entity(tableName = "states")
public class State{
#PrimaryKey(autoGenerate = false)
private int id;
private String name;
#ColumnInfo(name = "countryId")
private String CountryId;
#Ignore
private Object geoCenter, geoLimit;
public State(){
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCountryId() {
return CountryId;
}
public void setCountryId(String countryId) {
CountryId = countryId;
}
}
StateDAO
#Dao
public interface StateDao {
#Query("SELECT * FROM states")
LiveData<List<State>> getAllStates();
#Query("SELECT * FROM states WHERE countryId = :countryID")
LiveData<List<State>> getStatesFromCountry(String countryID);
#Query("SELECT COUNT(*) FROM states")
int getNrStates();
#Query("SELECT COUNT(*) FROM states WHERE countryId = :countryID")
int getNrStatesByCountry(String countryID);
#Insert(onConflict = IGNORE)
void insertAll(List<State> states);
#Delete
void delete(State state);
}
StateRepository
#Singleton
public class StatesRepository {
private final WebServices services;
private final StateDao stateDao;
private final Executor executor;
#Inject
public StatesRepository(Executor executor, StateDao stateDao, WebServices services) {
this.services = services;
this.stateDao = stateDao;
this.executor = executor;
}
public LiveData<List<State>> getStates(String token){
refreshStates(token);
return stateDao.getAllStates();
}
public LiveData<List<State>> getStatesFromCountry(String countryID){
return stateDao.getStatesFromCountry(countryID);
}
private void refreshStates(final String token){
executor.execute(() -> {
Log.d("oooooo", stateDao.getNrStates() + "");
if(stateDao.getNrStates() == 0){
try {
Response<List<State>> response = services.getStates("Bearer "+token).execute();
stateDao.insertAll(response.body());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
StateViewModel
public class StatesViewModel extends ViewModel {
private LiveData<List<State>> states;
private StatesRepository repo;
#Inject
public StatesViewModel(StatesRepository repository){
this.repo = repository;
}
public void init(String token){
states = repo.getStates(token);
}
public void getStatesFromCountry(String countryID){
states = repo.getStatesFromCountry(countryID);
}
public LiveData<List<State>> getStates(){
return this.states;
}
}
Fragment
public class EditAddressFragment extends LifecycleFragment implements View.OnClickListener, Injectable{
private Spinner country, city, state, zip_code;
private String token;
private List<Country> countries;
private List<City> cities;
private List<State> states;
#Inject ViewModelFactory viewModelFactory;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.addresses_edit_layout, container, false);
city = view.findViewById(R.id.city);
state = view.findViewById(R.id.state);
country = view.findViewById(R.id.country);
...
countries = new ArrayList<>();
cities = new ArrayList<>();
states = new ArrayList<>();
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
CountrySpinnerAdapter adapter = new CountrySpinnerAdapter(getActivity(), android.R.layout.simple_spinner_item, countries);
country.setAdapter(adapter);
CitySpinnerAdapter cityAdapter = new CitySpinnerAdapter(getActivity(), android.R.layout.simple_spinner_item, cities);
city.setAdapter(cityAdapter);
StateSpinnerAdapter stateAdapter = new StateSpinnerAdapter(getActivity(), android.R.layout.simple_spinner_item, states);
state.setAdapter(stateAdapter);
CountriesViewModel countriesViewModel = ViewModelProviders.of(this, viewModelFactory).get(CountriesViewModel.class);
countriesViewModel.init(token);
countriesViewModel.getCountries().observe(this, adapter::setValues);
CityViewModel cityViewModel = ViewModelProviders.of(this, viewModelFactory).get(CityViewModel.class);
cityViewModel.init(token);
cityViewModel.getCities().observe(this, cityAdapter::setValues);
StatesViewModel statesViewModel = ViewModelProviders.of(this, viewModelFactory).get(StatesViewModel.class);
statesViewModel.init(token);
statesViewModel.getStates().observe(this, states -> {
Log.d("called", states.toString());
stateAdapter.setValues(states); } );
country.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
Country c = (Country) adapterView.getItemAtPosition(i);
Log.d("cd", c.getId());
//states = new ArrayList<State>();
statesViewModel.getStatesFromCountry(c.getId());
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
....
Adapter
public void setValues(List<State> states)
{
this.states = states;
Log.d("s", states.isEmpty()+" "+states.toString());
notifyDataSetChanged();
}
Well, I have reached a solution for this issue and found out how this LiveData things works.
Thanks to #MartinMarconcini for all his help is debugging ;)
So apparently, the observers are linked to the object you first set it up to. You cannot replace the object (by attribution) or otherwise it will not work.
Also, if the value of your variable is going to change then you should use MutableLiveData
So the change necessary were:
1. Change from LiveData to MutableLiveData and pass that MutableLiveData to the repository when you need to update it
public class StatesViewModel extends ViewModel {
private MutableLiveData<List<State>> states; ;;CHANGED
private StatesRepository repo;
#Inject
public StatesViewModel(StatesRepository repository){
this.repo = repository;
}
public void init(String token){
states = repo.getStates(token);
}
public void getStatesFromCountry(String countryID){
repo.getStatesFromCountry(this.states, countryID); ;;CHANGED
}
public LiveData<List<State>> getStates(){
return this.states;
}
}
2. In the repository, update the MutableLiveData using setValue
#Singleton
public class StatesRepository {
private final WebServices services;
private final StateDao stateDao;
private final Executor executor;
#Inject
public StatesRepository(Executor executor, StateDao stateDao, WebServices services) {
this.services = services;
this.stateDao = stateDao;
this.executor = executor;
}
public MutableLiveData<List<State>> getStates(String token){
refreshStates(token);
final MutableLiveData<List<State>> data = new MutableLiveData<>();
data.setValue(stateDao.getAllStates());
return data;
}
;; CHANGED
public void getStatesFromCountry(MutableLiveData states, final String countryID){
states.setValue(stateDao.getStatesFromCountry(countryID));
}
private void refreshStates(final String token){
executor.execute(() -> {
if(stateDao.getNrStates() == 0){
try {
Response<List<State>> response = services.getStates("Bearer "+token).execute();
stateDao.insertAll(response.body());
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
3. Changed the DAO to return List instead of LiveData>
#Dao
public interface StateDao {
#Query("SELECT * FROM states")
List<State> getAllStates();
#Query("SELECT * FROM states WHERE ctrId = :countryID")
List<State> getStatesFromCountry(String countryID);
#Query("SELECT COUNT(*) FROM states")
int getNrStates();
#Query("SELECT COUNT(*) FROM states WHERE ctrId = :countryID")
int getNrStatesByCountry(String countryID);
#Insert(onConflict = IGNORE)
void insertAll(List<State> states);
#Delete
void delete(State state);
}
4.Finally allow to perform queries in the main thread
AppModule.java
#Singleton #Provides
AppDatabase provideDb(Application app) {
return Room.databaseBuilder(app, AppDatabase.class,"unitail.db")
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build();
}
Dao must be same across all operations.
You use different Dao instance for insert and observe
You should not update the livedata reference once you set it and start observing it.Instead to update the live data with repository you should use the MediatorLiveData.
In your case do the following changes:
private MediatorLiveData<List<State>> states; // change
.....
.....
states.addSource(repo.getStatesFromCountry(countryID), newData -> states.setValue(newData)); //change
Writing an answer for better discussion.
So I have (in Kotlin, sry) a model that is a list of notes (it’s just a sandbox app to play w/all this) and here’s my architecture: I don’t have a Repo, but I have Activity -> ViewModel -> Dao.
So Dao exposes a LiveData<MutableList<Note>>
#Query("SELECT * FROM notes")
fun loadAll(): LiveData<MutableList<Note>>
My ViewModel… exposes it through:
val notesList = database.notesDao().loadAll()
and my Activity (onCreate) does…
viewModel.notesList.observe(this,
Observer<MutableList<Note>> { notes ->
if (notes != null) {
progressBar?.hide()
adapter.setNotesList(notes)
}
})
This works. The adapter is a RecyclerView adapter that does literally nothing but:
fun setNotesList(newList: MutableList<Note>) {
if (notes.isEmpty()) {
notes = newList
notifyItemRangeInserted(0, newList.size)
} else {
val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return notes.size
}
override fun getNewListSize(): Int {
return newList.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return notes[oldItemPosition].id == newList[newItemPosition].id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val (id, title, _, priority) = newList[newItemPosition]
val (id1, title1, _, priority1) = notes[oldItemPosition]
return id == id1
&& priority == priority1
&& title == title1
}
})
notes = newList
result.dispatchUpdatesTo(this)
}
}
If ANY other part of the app modifies that list of notes, the adapter updates automagically. I hope this gives you a playground to try a simple(r?) approach.

Categories

Resources