I am using Room DB to store user set Price Alerts.
The Alerts list is shown in a Fragment with a button which launches new activity to add new entry.
When I add a new entry and go back to fragment, the RecyclerView isn't being updated even though I am returning LiveData and added an Observer.
I have tried multiple answers here like Using a MutableLiveData instead of LiveData and few other but it still isn't working.
I have tried multiple answers here like Using a MutableLiveData instead of LiveData and few other but it still isn't working.
DOA
#Dao
public interface PriceAlertDoa {
public String tableName = DBContract.ALERTS_TABLE_NAME;
#Query("SELECT * FROM " + tableName)
public LiveData<List<PriceAlert>> getAll();
#Query("SELECT * FROM " + tableName + " WHERE " + DBContract.ALERTS_COL_COINID + " LIKE :coinId")
public List<PriceAlert> getByCoinId(String coinId);
#Query("SELECT COUNT(*) FROM " + DBContract.ALERTS_TABLE_NAME)
public int countUsers();
#Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertAll(PriceAlert... priceAlerts);
#Delete
public void deleteAll(PriceAlert... priceAlert);
#Update
public void updateAll(PriceAlert... priceAlert);
}
Repository
public class PriceAlertRepository {
private PriceAlertDatabase alertsDb;
private Context mContext;
public PriceAlertRepository(Context context){
mContext = context;
if(alertsDb == null){
alertsDb = Room.databaseBuilder(mContext.getApplicationContext(),
PriceAlertDatabase.class, DBContract.ALERTS_TABLE_NAME)
.build();
}
}
public void insertAlert(PriceAlert priceAlert) {
new AsyncTask<Void, Void, Void>(){
#Override
protected Void doInBackground(Void... voids) {
alertsDb.priceAlertDoa().insertAll(priceAlert);
return null;
}
}.execute();
}
public LiveData<List<PriceAlert>> getAllAlerts(){
return alertsDb.priceAlertDoa().getAll();
}
public void deleteAlert(PriceAlert priceAlert){
new AsyncTask<Void, Void, Void>(){
#Override
protected Void doInBackground(Void... voids) {
alertsDb.priceAlertDoa().deleteAll(priceAlert);
return null;
}
}.execute();
}
public void updateAlert(PriceAlert priceAlert){
new AsyncTask<Void, Void, Void>(){
#Override
protected Void doInBackground(Void... voids) {
alertsDb.priceAlertDoa().updateAll(priceAlert);
return null;
}
}.execute();
}
}
ViewModel
public class AlertsViewModel extends AndroidViewModel {
private PriceAlertRepository mAlertRepository;
private LiveData<List<PriceAlert>> mAlertList;
public AlertsViewModel(#NonNull Application application) {
super(application);
mAlertRepository = new PriceAlertRepository(application);
mAlertList = mAlertRepository.getAllAlerts();
}
public LiveData<List<PriceAlert>> getAllAlerts(){
return mAlertList;
}
}
The Fragment displaying List of Alerts
public class AlertsListFragment extends Fragment {
#BindView(R.id.recyclerView_Alerts)
RecyclerView rvAlerts;
#BindView(R.id.button_AddAlert)
Button btnAddAlert;
private Context mContext;
private AlertsViewModel alertsViewModel;
public AlertsListFragment(Context context) {
mContext = context;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_alerts_list, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
rvAlerts.setHasFixedSize(true);
rvAlerts.setLayoutManager(new LinearLayoutManager(mContext));
alertsViewModel = ViewModelProviders.of(this).get(AlertsViewModel.class);
btnAddAlert.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Utils.launchActivity(mContext, AddAlertActivity.class);
}
});
AlertsListAdapter adapter = new AlertsListAdapter(mContext);
rvAlerts.setAdapter(adapter);
alertsViewModel.getAllAlerts().observe(getViewLifecycleOwner(), new Observer<List<PriceAlert>>() {
#Override
public void onChanged(List<PriceAlert> priceAlerts) {
Log.d("LIVE DATA", "Table updated: " + priceAlerts.size());
adapter.updateData(priceAlerts);
}
});
}
}
Thanks in advance.
Related
I am trying to get some data from a website and load it into my app.
The app has 1 MainActivity and 3 fragments and uses ViewBinding.
Unfortunately I can not create a instance of viewmodel in the 1st Fragment.
I already searched stackoverflow and the internet (a lot) but I somehow can not get all the solutions given working.
I get the exception on materialViewModel.getMaterials().observe in the HomeFragment.
Any help is highly appreciated.
My HomeFragment:
public class HomeFragment extends Fragment {
private MaterialViewModel materialViewModel;
private FragmentHomeBinding binding;
ArrayList<MaterialModel> posts;
public HomeFragment() {
}
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
binding = FragmentHomeBinding.inflate(inflater, container, false);
materialViewModel = new ViewModelProvider(getActivity()).get(MaterialViewModel.class);
initializeRecyclerView();
getPosts();
return binding.getRoot();
}
private void initializeRecyclerView() {
posts = new ArrayList<>();
binding.rvMain.setLayoutManager(new LinearLayoutManager(getActivity()));
}
private void getPosts() {
materialViewModel.getMaterials().observe(getActivity(), new Observer<List<MaterialModel>>() {
#Override
public void onChanged(#Nullable List<MaterialModel> posts) {
binding.rvMain.setAdapter(new ItemAdapter((ArrayList<MaterialModel>) posts));
}
});
getIsLoaded();
}
public void getIsLoaded(){
materialViewModel.getIsLoaded().observe(getActivity(), new Observer<Boolean>() {
#Override
public void onChanged(#Nullable Boolean aBoolean) {
if (aBoolean == true){
//activityMainBinding.progressBarID.setVisibility(View.GONE);
Log.w("PAR-LOADED ", "DATA IS LOADED");
}
}
});
}
#Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
My MaterialViewmodel:
public class MaterialViewModel extends AndroidViewModel {
private MutableLiveData<List<MaterialModel>> matts;
private MaterialRepository materialRepository;
public MaterialViewModel(Application application) {
super(application);
materialRepository = new MaterialRepository(application);
}
public void init() {
if (this.matts == null) {
matts = materialRepository.getAllMaterials();
}
}
public LiveData<List<MaterialModel>> getMaterials() {
return this.matts;
}
public MutableLiveData<Boolean> getIsLoaded() {
return materialRepository.dataIsLoaded;
}
}
My MaterialRepository:
public class MaterialRepository {
API_Service service;
private String posts;
ArrayList<String> arrayLiNames = new ArrayList<>();
ArrayList<String> arrayLiLinks = new ArrayList<>();
public ArrayList<MaterialModel> materialData = new ArrayList<>();
public MutableLiveData<List<MaterialModel>> mattis;
public MutableLiveData<Boolean> dataIsLoaded;
public MaterialRepository(Application application) {
super();
service = RetrofitClass.getClient(application).create(API_Service.class);
}
public MutableLiveData<List<MaterialModel>> getAllMaterials() {
Call<List<MaterialModel>> postResponseCall = service.getMaterials();
postResponseCall.enqueue(new Callback<List<MaterialModel>>() {
#Override
public void onResponse(Call<List<MaterialModel>> call, Response<List<MaterialModel>> response) {
if (response.code() == 200) {
Log.w("PAR-RESPONSE-CODE ", String.valueOf(response.code()));
posts = String.valueOf(response.body());
//new MyTask().execute();
}
}
#Override
public void onFailure(Call<List<MaterialModel>> call, Throwable t) {
}
});
return mattis;
}
private class MyTask extends AsyncTask<Void, Void, List<MaterialModel>> {
...
#Override
protected List<MaterialModel> doInBackground(Void... params) {
#Override
protected void onPostExecute(List<MaterialModel> result) {
mattis.setValue(result);
dataIsLoaded.setValue(true);
}
}
}
PreAdmissionList.java
public class PreAdmissionList extends Fragment implements View.OnClickListener, AdapterApprovalList.OnItemClickListener {
private BasicInfoViewModel basicInfoViewModel;
private AdapterApprovalList adapterApprovalList;
private RecyclerView rvApprovalList;
public PreAdmissionList() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_pre_admission_list, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.findViewById(R.id.fab_add_new_admission).setOnClickListener(this);
rvApprovalList = view.findViewById(R.id.rv_approval_list);
basicInfoViewModel = new ViewModelProvider(requireActivity()).get(BasicInfoViewModel.class);
basicInfoViewModel.init();
basicInfoViewModel.getApprovalList().observe(getViewLifecycleOwner(), new Observer<List<ModelBasicInfo>>() {
#Override
public void onChanged(List<ModelBasicInfo> modelBasicInfos) {
adapterApprovalList.notifyDataSetChanged();
}
});
initRecyclerView();
}
private void initRecyclerView() {
adapterApprovalList = new AdapterApprovalList(this,basicInfoViewModel.getApprovalList().getValue());
rvApprovalList.setHasFixedSize(true);
rvApprovalList.setLayoutManager(new LinearLayoutManager(getContext()));
rvApprovalList.setAdapter(adapterApprovalList);
}
}
AdapterApprovalList.java
public class AdapterApprovalList extends RecyclerView.Adapter<AdapterApprovalList.ALViewHolder>{
private Context context;
private OnItemClickListener onItemClickListener;
private List<ModelBasicInfo> modelBasicInfoList;
public AdapterApprovalList(OnItemClickListener onItemClickListener,List<ModelBasicInfo> modelBasicInfoList) {
this.onItemClickListener = onItemClickListener;
this.modelBasicInfoList=modelBasicInfoList;
}
#NonNull
#Override
public ALViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
View view = LayoutInflater.from(context).inflate(R.layout.template_approval_list_item,parent,false);
return new ALViewHolder(view,onItemClickListener);
}
#Override
public void onBindViewHolder(#NonNull ALViewHolder holder, int position) {
ModelBasicInfo basicInfo = modelBasicInfoList.get(position);
StringBuilder fullName = new StringBuilder();
fullName.append(basicInfo.getFirstName()).append(" ");
fullName.append(basicInfo.getMiddleName()).append(" ");
fullName.append(basicInfo.getLastName()).append(" ");
holder.fullName.setText(fullName);
holder.id.setText("RKC00"+String.valueOf(basicInfo.getId()));
}
#Override
public int getItemCount() {
return modelBasicInfoList.size();
}
static class ALViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
OnItemClickListener mOnItemClickListener;
TextView fullName,id;
public ALViewHolder(#NonNull View itemView,OnItemClickListener mOnItemClickListener) {
super(itemView);
this.mOnItemClickListener = mOnItemClickListener;
fullName = itemView.findViewById(R.id.tv_text_full_name);
id = itemView.findViewById(R.id.tv_text_approval_id);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
mOnItemClickListener.onApprovalItemClick(getAbsoluteAdapterPosition());
}
}
public interface OnItemClickListener{
void onApprovalItemClick(int position);
}
}
BasicInfoViewModel.java
public class BasicInfoViewModel extends ViewModel {
private BasicInfoRepo basicInfoRepo;
private MutableLiveData<List<ModelBasicInfo>> approvalList;
public void init(){
if(approvalList != null){
return;
}
basicInfoRepo = BasicInfoRepo.getInstance();
approvalList = basicInfoRepo.getApprovalList();
}
public LiveData<List<ModelBasicInfo>> getApprovalList(){
return approvalList;
}
public void insertBasicInfo(ModelBasicInfo modelBasicInfo){
basicInfoRepo.insertData(modelBasicInfo);
}
public void updateApprovalStatus(int id){
basicInfoRepo.updateStatus(id);
}
}
BasicInfoRepo.java
public class BasicInfoRepo {
private static BasicInfoRepo instance;
static ConnectionClass connectionClass = new ConnectionClass();
private List<ModelBasicInfo> approvalList = new ArrayList<>();
public static BasicInfoRepo getInstance(){
if(instance== null){
instance = new BasicInfoRepo();
}
return instance;
}
public MutableLiveData<List<ModelBasicInfo>> getApprovalList(){
loadApprovalList();
MutableLiveData<List<ModelBasicInfo>> mList = new MutableLiveData<>();
mList.setValue(approvalList);
return mList;
}
private void loadApprovalList() {
LoadApprovalList loadApprovalList = new LoadApprovalList();
loadApprovalList.execute();
}
public void insertData(ModelBasicInfo modelBasicInfo){
InsertBasicInfo insertBasicInfo = new InsertBasicInfo();
insertBasicInfo.execute(modelBasicInfo);
}
public void updateStatus(int id){
UpdateBasicInfo updateBasicInfo = new UpdateBasicInfo();
updateBasicInfo.execute(id);
}
private static class InsertBasicInfo extends AsyncTask<ModelBasicInfo,Integer,String>{
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected String doInBackground(ModelBasicInfo... model) {
String result = null;
// Log.i("Testing db",lists[0].get(0).getFirstName());
try{
Connection connection = connectionClass.CONN();
if(connection==null){
result = "Error in connection !!!";
}else{
//Date object
Date date= new Date();
//getTime() returns current time in milliseconds
long time = date.getTime();
//Passed the milliseconds to constructor of Timestamp class
Timestamp ts = new Timestamp(time);
PreparedStatement ps = connection.prepareStatement("insert into PreAdmissionDetails values(?,?,?,?,?,?,?,?,?,?)");
ps.setString(1,model[0].getFirstName());
ps.setString(2,model[0].getMiddleName());
ps.setString(3,model[0].getLastName());
ps.setString(4,model[0].getMotherName());
ps.setDate(5, java.sql.Date.valueOf(model[0].getDateOfBirth()));
ps.setString(6,model[0].getMobileNo());
ps.setInt(7,0);
ps.setInt(8,0);
ps.setBoolean(9,false);
ps.setTimestamp(10, ts);
ps.executeUpdate();
result = "Submitted Successfully !!!";
}
}catch (Exception ex){
Log.e("sqlerror",ex.toString());
result=ex.getMessage();
}
Log.e("sqlerror","result : "+result);
return result;
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
}
}
private static class UpdateBasicInfo extends AsyncTask<Integer,Integer,String>{
#Override
protected String doInBackground(Integer... integers) {
String result = null;
try{
Connection connection = connectionClass.CONN();
if(connection==null){
result = "Error in connection !!!";
}else{
PreparedStatement ps = connection.prepareStatement("UPDATE PreAdmissionDetails SET STATUS=? WHERE id=?");
ps.setInt(1,0);
ps.setInt(2,integers[0]);
ps.executeUpdate();
result = "Updated Successfully !!!";
}
}catch (Exception ex){
Log.e("sqlerror",ex.toString());
result=ex.getMessage();
}
Log.e("sqlerror","result : "+result.toString());
return result;
}
}
private class LoadApprovalList extends AsyncTask<Void,Void,Void>{
#Override
protected Void doInBackground(Void... voids) {
String result = null;
try{
Connection connection = connectionClass.CONN();
if(connection==null){
result = "Error in connection !!!";
}else{
PreparedStatement ps = connection.prepareStatement("select * from preadmissiondetails");
ResultSet rs = ps.executeQuery();
approvalList.clear();
while (rs.next()) {
approvalList.add(new ModelBasicInfo(rs.getInt(1),
rs.getString(2),
rs.getString(3),
rs.getString(4),
rs.getString(5),
rs.getString(6),
rs.getString(7),
rs.getInt(8),
rs.getInt(9),
rs.getBoolean(10)));}
result = "Fetched Successfully !!!";
}
}catch (Exception ex){
Log.e("sqlerror",ex.toString());
result=ex.getMessage();
}
Log.e("sqlerror","result : "+result.toString());
return null;
}
}
}
Problem is here that when I open the app no items in the recycler view, Firsty I thought may be slow internet it will fetch data in after some time but it doesn't show data. But when I navigate to some other fragments and return to the preadmission list it shows data.
Your problem is here:
public MutableLiveData<List<ModelBasicInfo>> getApprovalList(){
loadApprovalList();
MutableLiveData<List<ModelBasicInfo>> mList = new MutableLiveData<>();
mList.setValue(approvalList);
return mList;
}
loadApprovalList() launches an AsyncTask, which is an asynchronous operation (in other words, it takes time to produce a result). The getApprovalList() method doesn't just halt right there and wait for loadApprovalList() to complete. It continues right along and returns an empty list the first time it executes. But by the second time it executes, approvalList now has a value because the AsyncTask has completed. So it returns the correct data that second time it executes. It executes a second time when you return to your Fragment because the init block in your ViewModel is executing a second time at that point.
The solution is to make approvalList a LiveData. That way, when the AsyncTask updates approvalList, your ViewModel can observe the change. Your ViewModel should observe approvalList in your Repository, just like how your Fragment is observing the getApprovalList() method in your ViewModel.
I am writing a simple application that stores 2 string values in a database using Room (I am trying to learn this library). So, I have only one line on my list. Presented for the first time. The rest are not displayed. What is the reason for this behavior?
Model
#Entity
public class Note {
#PrimaryKey(autoGenerate = true)
private long id;
private String title;
private String text;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
NoteDao
#Dao
public interface NoteDao {
#Insert
void insert(Note note);
#Delete
void delete(Note note);
#Query("SELECT * FROM Note")
List<Note> getAllNotes();
}
AppDatabase
#Database(entities = Note.class, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract NoteDao getNoteDao();
}
DataManager
public class DataManager {
private AppDatabase appDatabase;
public DataManager (AppDatabase appDatabase) {
this.appDatabase = appDatabase;
}
public List <Note> getNotes () {
try {
return new GetNotes (). execute (). get ();
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace ();
return null;
}
}
public void insertNote (Note note) {
new InsertNote (note) .execute ();
}
public void deleteNote (Note note) {
new DeleteNote (note) .execute ();
}
// Get all notes
public class GetNotes extends AsyncTask <Void, Void, List <Note>> {
#Override
protected List <Note> doInBackground (Void ... voids) {
return appDatabase.getNoteDao (). getAllNotes ();
}
}
// Insert note
public class InsertNote extends AsyncTask <Void, Void, Void> {
private Note note;
InsertNote (Note note) {
this.note = note;
}
#Override
protected Void doInBackground (Void ... voids) {
appDatabase.getNoteDao (). insert (note);
return null;
}
}
// Delete note
public class DeleteNote extends AsyncTask <Void, Void, Void> {
private Note note;
public DeleteNote (Note note) {
this.note = note;
}
#Override
protected Void doInBackground (Void ... voids) {
appDatabase.getNoteDao (). delete (note);
return null;
}
}
}
Mvp
public interface NoteListView extends MvpView {
void showNoteList(List<Note> note);
}
Presenter
public class NoteListPresenter extends MvpPresenter<NoteListView> {
private DataManager dataManager;
public NoteListPresenter(DataManager dataManager) {
this.dataManager = dataManager;
}
public void getNotes(){
getView().showNoteList(dataManager.getNotes());
}
public void deleteNote(Note note) {
dataManager.deleteNote(note);
getView().showNoteList(dataManager.getNotes());
}
}
Adapter
public class NoteAdapter extends RecyclerView.Adapter<NoteAdapter.ViewHolder> {
private List<Note> listNote;
private Context context;
public NoteAdapter(Context context, List<Note> listNote) {
this.listNote = listNote;
this.context = context;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
holder.title.setText(listNote.get(position).getTitle());
holder.text.setText(listNote.get(position).getText());
}
#Override
public int getItemCount() {
return listNote.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView title, text;
public ViewHolder(#NonNull View itemView) {
super(itemView);
title = itemView.findViewById(R.id.title);
text = itemView.findViewById(R.id.text);
}
}
}
MainActivity
public class MainActivity extends AppCompatActivity implements NoteListView {
private NoteListPresenter presenter;
private RecyclerView recyclerView;
private NoteAdapter noteAdapter;
private ConstraintLayout constraintLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
constraintLayout = findViewById(R.id.coordinatorMain);
recyclerView = findViewById(R.id.recycler);
recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
presenter = new NoteListPresenter(App.getDataManager());
presenter.attachView(this);
presenter.getNotes();
}
public void onNextActivity(View view){
startActivity(new Intent(MainActivity.this, AddNoteActivity.class));
}
#Override
public void showNoteList(List<Note> note) {
noteAdapter = new NoteAdapter(this, note);
recyclerView.setAdapter(noteAdapter);
}
#Override
protected void onResume() {
super.onResume();
presenter.getNotes();
}
#Override
public void showMessage(String message) {
Snackbar.make(constraintLayout, message, Snackbar.LENGTH_SHORT).show();
}
}
UPD
SaveNoteView
public interface SaveNoteView extends MvpView {
void insertNote(Note note);
}
SaveNotePresenter
public class SaveNotePresenter extends MvpPresenter<SaveNoteView> {
private DataManager dataManager;
public SaveNotePresenter(DataManager dataManager) {
this.dataManager = dataManager;
}
public void insertNote(Note note){
dataManager.insertNote(note);
getView().insertNote(note);
}
}
AddNoteActivity
public class AddNoteActivity extends AppCompatActivity implements SaveNoteView {
private TextInputEditText title, text;
private ConstraintLayout constraintLayout;
private SaveNotePresenter presenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_note);
constraintLayout = findViewById(R.id.constrainAdd);
title = findViewById(R.id.titleEditText);
text = findViewById(R.id.textEditText);
presenter = new SaveNotePresenter(App.getDataManager());
presenter.attachView(this);
}
//Save a note
public void saveNote(View view){
Note note = new Note();
note.setTitle(title.getText().toString());
note.setText(text.getText().toString());
presenter.insertNote(note);
finish();
}
#Override
public void insertNote(Note note) {
DataManager dataManager = App.getDataManager();
dataManager.insertNote(note);
}
#Override
public void showMessage(String message) {
Snackbar.make(constraintLayout, message, Snackbar.LENGTH_SHORT).show();
}
}
It's pretty likely that there is only 1 id which is beeing replaced the whole time (if this is not the case, check how often insertion is done)
I used Kotlin with Room but in this official example they have a public autogenerated primaryKey, which might be required for Room to access it and autogenerate it. So make the variable public and see if it works
There was no problem. I just specified the parent layout for item_list in full screen. Therefore, the subsequent ones were not visible. Silly mistake:D
I have problem with observing 3 different lists with live data observer for viewing them in one single recycler view.
This is the scenario: In ListsFragment, I have TabLayout with 3 tabs (WANT, PLAYING, PLAYED) and 1 recycler view. On different tab click I load different lists of games from database and I am presenting them with Live Data observer pattern in the recycler view. Until this moment everything works OK.
The problem occurs when I press one game DELETE button, the database is updated but in the recycler view is loading wrong list and not the list of the selected tab (Probably because there are 3 active observers), so the data on the screen is updated with wrong games.
Gif of the behaviour:
https://gfycat.com/showyembarrassedbarasingha
Can anyone please help me, I tried many solutions but nothing helped and I am stuck more than 1 month.
This is the code.
Fragment:
public class ListsFragment extends Fragment implements VisitedGameInterface {
public static final String TAG = ListsFragment.class.getSimpleName();
private static final int WANT = 0;
private static final int PLAYING = 1;
private static final int PLAYED = 2;
private int selectedTab = WANT;
private GameViewModel gameViewModel;
private GameVisitedAdapter gameAdapter;
private TextView noGamesTV;
public ListsFragment() {
// Required empty public constructor
}
public static ListsFragment newInstance(Bundle bundle) {
ListsFragment fragment = new ListsFragment();
fragment.setArguments(bundle);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_lists, container, false);
TabLayout listsTabLayout = view.findViewById(R.id.lists_tab_layout);
RecyclerView listsRecyclerView = view.findViewById(R.id.lists_recycler_view);
noGamesTV = view.findViewById(R.id.no_games_tv);
listsTabLayout.addTab(listsTabLayout.newTab().setText(getString(R.string.want)), true);
listsTabLayout.addTab(listsTabLayout.newTab().setText(getString(R.string.playing)));
listsTabLayout.addTab(listsTabLayout.newTab().setText(getString(R.string.played)));
listsTabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
listsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), RecyclerView.VERTICAL, false));
listsRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));
gameAdapter = new GameVisitedAdapter(getActivity(), this);
listsRecyclerView.setAdapter(gameAdapter);
gameViewModel = ViewModelProviders.of(this).get(GameViewModel.class);
gameViewModel.getWantedGames().observe(this, new Observer<List<Game>>() {
#Override
public void onChanged(List<Game> wantedGames) {
if (wantedGames.size() != 0) {
noGamesTV.setVisibility(View.GONE);
}else {
noGamesTV.setText(R.string.no_wanted_games);
noGamesTV.setVisibility(View.VISIBLE);
}
gameAdapter.setGames(wantedGames);
gameAdapter.notifyDataSetChanged();
}
});
listsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
if (getActivity() != null && tab.getText() != null) {
if (tab.getText().equals("WANT")) {
selectedTab = tab.getPosition();
gameViewModel.getWantedGames().observe(getActivity(), new Observer<List<Game>>() {
#Override
public void onChanged(List<Game> wantedGames) {
if (wantedGames.size() != 0) {
noGamesTV.setVisibility(View.GONE);
} else {
noGamesTV.setText(R.string.no_wanted_games);
noGamesTV.setVisibility(View.VISIBLE);
}
gameAdapter.setGames(wantedGames);
gameAdapter.notifyDataSetChanged();
}
});
} else if (tab.getText().equals("PLAYING")) {
selectedTab = tab.getPosition();
gameViewModel.getPlayingGames().observe(getActivity(), new Observer<List<Game>>() {
#Override
public void onChanged(List<Game> playingGames) {
if (playingGames.size() != 0) {
noGamesTV.setVisibility(View.GONE);
} else {
noGamesTV.setText(R.string.no_playing_games);
noGamesTV.setVisibility(View.VISIBLE);
}
gameAdapter.setGames(playingGames);
gameAdapter.notifyDataSetChanged();
}
});
} else if (tab.getText().equals("PLAYED")){
selectedTab = tab.getPosition();
gameViewModel.getPlayedGames().observe(getActivity(), new Observer<List<Game>>() {
#Override
public void onChanged(List<Game> playedGames) {
if (playedGames.size() != 0) {
noGamesTV.setVisibility(View.GONE);
} else {
noGamesTV.setText(R.string.no_played_games);
noGamesTV.setVisibility(View.VISIBLE);
}
gameAdapter.setGames(playedGames);
gameAdapter.notifyDataSetChanged();
}
});
}
}
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
return view;
}
#Override
public void onGameDelete(final Game game) {
if (selectedTab == WANT){
game.setWanted(false);
showSnackBar(game, WANT);
} if (selectedTab == PLAYING){
game.setPlaying(false);
showSnackBar(game, PLAYING);
} if (selectedTab == PLAYED){
game.setPlayed(false);
showSnackBar(game, PLAYED);
}
gameViewModel.update(game);
gameAdapter.notifyDataSetChanged();
}
#Override
public void onVisitedGameClick(Game game) {
Intent intent = new Intent(getActivity(), GameActivity.class);
intent.putExtra("game", game);
startActivity(intent);
}
private void showSnackBar(final Game snackGame, final int selectedTab){
Snackbar snackbar = Snackbar.make(Objects.requireNonNull(getView()), snackGame.getName() + " deleted", Snackbar.LENGTH_LONG)
.setAction(R.string.undo, new View.OnClickListener() {
#Override
public void onClick(View view) {
if (selectedTab == WANT) snackGame.setWanted(true);
if (selectedTab == PLAYING) snackGame.setPlaying(true);
if (selectedTab == PLAYED) snackGame.setPlayed(true);
gameViewModel.insert(snackGame);
}
});
snackbar.show();
}
}
XML of ListsFragment
<RelativeLayout
android:id="#+id/lists_layout" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
tools:context=".fragment.ListsFragment">
<com.google.android.material.tabs.TabLayout
android:id="#+id/lists_tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabSelectedTextColor="#color/button_blue"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/lists_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#id/lists_tab_layout"/>
<TextView
android:id="#+id/no_games_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
tools:text="No games at this moment"
android:textColor="#color/text_black"
android:visibility="invisible"/>
</RelativeLayout>
The adapter:
public class GameVisitedAdapter extends RecyclerView.Adapter<GameVisitedAdapter.GameVisitedViewHolder> {
private Context context;
private LayoutInflater inflater;
private List<Game> games = new ArrayList<>();
private VisitedGameInterface visitedGameInterface;
public GameVisitedAdapter(Context context, VisitedGameInterface visitedGameInterface){
this.context = context;
inflater = LayoutInflater.from(context);
this.visitedGameInterface = visitedGameInterface;
}
public void setGames(List<Game> games){
this.games = games;
}
#NonNull
#Override
public GameVisitedViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.item_game_visited, parent, false);
return new GameVisitedViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull GameVisitedViewHolder holder, int position) {
Game game = games.get(position);
if (game.getCover() != null) {
Glide
.with(context)
.load(context.getString(R.string.cover_url) + game.getCover().getImage_id() + ".jpg")
.centerCrop()
.placeholder(context.getResources().getDrawable(R.drawable.placeholder))
.into(holder.gameCover);
}
holder.gameTitle.setText(game.getName());
SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy");
String dateString = formatter.format((new Date((long) game.getFirst_release_date() * 1000)));
holder.gameYear.setText(dateString);
}
#Override
public int getItemCount() {
return games.size();
}
class GameVisitedViewHolder extends RecyclerView.ViewHolder {
private ImageView gameCover, gameDeleteButton;
private TextView gameTitle, gameYear;
GameVisitedViewHolder(#NonNull View itemView) {
super(itemView);
gameCover = itemView.findViewById(R.id.game_cover);
gameTitle = itemView.findViewById(R.id.game_name);
gameYear = itemView.findViewById(R.id.game_release_year);
gameDeleteButton = itemView.findViewById(R.id.game_delete_button);
gameDeleteButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
visitedGameInterface.onGameDelete(games.get(getAdapterPosition()));
}
});
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
visitedGameInterface.onVisitedGameClick(games.get(getAdapterPosition()));
}
});
}
}
}
The ViewModel
public class GameViewModel extends AndroidViewModel {
private GameRepository gameRepository;
public GameViewModel(#NonNull Application application) {
super(application);
gameRepository = new GameRepository(application);
}
public void insert(Game game){
gameRepository.insert(game);
}
public void update(Game game){
gameRepository.update(game);
}
public void deleteGame(Game game){
gameRepository.delete(game);
}
public void deleteAll(){
gameRepository.deleteAll();
}
public void DeleteVisitedGames(){ gameRepository.deleteVisitedGames();}
public LiveData<List<Game>> getWantedGames(){
return gameRepository.getWantedGames();
}
public LiveData<List<Game>> getPlayingGames(){
return gameRepository.getPlayingGames();
}
public LiveData<List<Game>> getPlayedGames(){
return gameRepository.getPlayedGames();
}
public LiveData<List<Game>> getVisitedGames(){
return gameRepository.getVisitedGames();
}
public LiveData<List<Game>> getAllGamesFromDB(){
return gameRepository.getAllGamesFromDB();
}
}
The Repository:
public class GameRepository {
private GameDao gameDao;
private LiveData<List<Game>> wantedGames;
private LiveData<List<Game>> playingGames;
private LiveData<List<Game>> playedGames;
private LiveData<List<Game>> visitedGames;
private LiveData<List<Game>> allGamesFromDB;
public GameRepository(Application application){
GameDatabase gameDatabase = GameDatabase.getInstance(application);
gameDao = gameDatabase.gameDao();
wantedGames = gameDao.getWantedGames();
playingGames = gameDao.getPlayingGames();
playedGames = gameDao.getPlayedGames();
visitedGames = gameDao.getVisitedGames();
allGamesFromDB = gameDao.getAllGamesFromDB();
}
public LiveData<List<Game>> getWantedGames(){
return wantedGames;
}
public LiveData<List<Game>> getPlayingGames(){
return playingGames;
}
public LiveData<List<Game>> getPlayedGames(){
return playedGames;
}
public LiveData<List<Game>> getVisitedGames(){
return visitedGames;
}
public LiveData<List<Game>> getAllGamesFromDB(){
return allGamesFromDB;
}
public void insert(Game game){
new InsertAsyncTask(gameDao).execute(game);
}
public void update(Game game){
new UpdateAsyncTask(gameDao).execute(game);
}
public void delete(Game game){
new DeleteItemAsyncTask(gameDao).execute(game);
}
public void deleteAll(){
new DeleteAllAsyncTask(gameDao).execute();
}
public void deleteVisitedGames() {new DeleteVisitedAsyncTask(gameDao).execute();}
//-------------INSERT GAME ASYNC TASK
private static class InsertAsyncTask extends AsyncTask<Game, Void, Void> {
private GameDao asyncDao;
public InsertAsyncTask(GameDao asyncDao){
this.asyncDao = asyncDao;
}
#Override
protected Void doInBackground(Game... games) {
asyncDao.insert(games[0]);
return null;
}
}
//-------------UPDATE GAME ASYNC TASK
private static class UpdateAsyncTask extends AsyncTask<Game, Void, Void> {
private GameDao asyncDao;
public UpdateAsyncTask(GameDao asyncDao){
this.asyncDao = asyncDao;
}
#Override
protected Void doInBackground(Game... games) {
asyncDao.updateGame(games[0]);
return null;
}
}
//----------DELETE GAME ASYNC TASK
private static class DeleteItemAsyncTask extends AsyncTask <Game, Void, Void> {
private GameDao asyncDao;
public DeleteItemAsyncTask(GameDao asyncDao){
this.asyncDao = asyncDao;
}
#Override
protected Void doInBackground(Game... games) {
asyncDao.deleteGame(games[0]);
return null;
}
}
//-------------DELETE ALL ASYNC TASK
private static class DeleteAllAsyncTask extends AsyncTask <Void, Void, Void>{
private GameDao asyncDao;
public DeleteAllAsyncTask(GameDao asyncDao){
this.asyncDao = asyncDao;
}
#Override
protected Void doInBackground(Void... voids) {
asyncDao.deleteAllGames();
return null;
}
}
//-------------DELETE VISITED GAMES ASYNC TASK
private static class DeleteVisitedAsyncTask extends AsyncTask <Void, Void, Void>{
private GameDao asyncDao;
public DeleteVisitedAsyncTask(GameDao asyncDao){
this.asyncDao = asyncDao;
}
#Override
protected Void doInBackground(Void... voids) {
asyncDao.deleteVisitedGames();
return null;
}
}
}
The DAO:
#Dao
public interface GameDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Game game);
#Delete
void deleteGame(Game game);
#Update
void updateGame(Game game);
#Query("DELETE FROM game_table")
void deleteAllGames();
#Query("SELECT * FROM game_table WHERE isWanted")
LiveData<List<Game>> getWantedGames();
#Query("SELECT * FROM game_table WHERE isPlaying")
LiveData<List<Game>> getPlayingGames();
#Query("SELECT * FROM game_table WHERE isPlayed")
LiveData<List<Game>> getPlayedGames();
#Query("SELECT * FROM game_table WHERE isVisited")
LiveData<List<Game>> getVisitedGames();
#Query("SELECT * FROM game_table")
LiveData<List<Game>> getAllGamesFromDB();
#Query("DELETE FROM game_table WHERE isVisited")
void deleteVisitedGames();
}
Database:
#Database(entities = Game.class, version = 8, exportSchema = false)
#TypeConverters({Converters.class})
public abstract class GameDatabase extends RoomDatabase {
public abstract GameDao gameDao();
private static GameDatabase INSTANCE;
public static GameDatabase getInstance(Context context){
if (INSTANCE == null){
synchronized (GameDatabase.class){
if (INSTANCE == null){
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
GameDatabase.class, "game_database")
.fallbackToDestructiveMigration()
.addCallback(sRoomDatabaseCallback)
.build();
}
}
}
return INSTANCE;
}
private static RoomDatabase.Callback sRoomDatabaseCallback = new RoomDatabase.Callback(){
#Override
public void onOpen(#NonNull SupportSQLiteDatabase db) {
super.onOpen(db);
}
};
}
My 2cents: even after you change tabs, your observers are still active, as you are using the same table for everything and I believe (just glance at the code) you are updating your game entry (e.g: isPlayed set to false) thus it send updates to all active observers thus sending update to the recylerview.
With that in mind, in case I am right here, you can:
Make 3 distinct tables (can be in the same database)
use viewpager to have 3 fragments with 3 diffent recyclers (actually you can use viewpager2 with a viewholder instead of making fragments)
have 3 recyclers (and you do a visibility.Gone every time user changes the tab)
In case I am not right, option 2 e 3 still solve your problem. I hope it helps!
p.s: you could also remove and insert observers everytime users changes tab, I find it to be less straightforward though.
I'm trying to use Room database in my Quiz application.
I couldn't get list of questions from database. here is the code I'm using :
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.new_exam, container, false);
queViewModel=ViewModelProviders.of(this).get(QueViewModel.class);
queViewModel.getQuizList(examType).observe(this, new Observer<List<QueEnt>>() {
#Override
public void onChanged(List<QueEnt> queEnts) {
Log.e(TAG, "onChanged is called!"); // this line never shows in the Logcat
questionsList2 = queEnts;
}
});
and this is the ViewModel :
public class QueViewModel extends AndroidViewModel {
private QueRepository repository;
private LiveData<List<QueEnt>> quesList;
public QueViewModel(#NonNull Application application) {
super(application);
repository=new QueRepository(application);
quesList=repository.getQues();
}
public void insert(QueEnt queEnt){
repository.insert(queEnt);
}
public void delete(QueEnt queEnt){
repository.delete(queEnt);
}
public LiveData<List<QueEnt>> getQuizList(int examtype){
Log.e(TAG,"returning a list"); // this appears in the Logcat
return quesList;
}
}
The error I get is
Invalid index 0, size is 0
on questionsList2 , of course because the list was never populated. The onChanged method is not called, as it seems from the Logcat.
How can I fix this ?
Edit :
QueRepository file:
public class QueRepository {
private QueDao queDao;
private LiveData<List<QueEnt>> ques;
public QueRepository(Application application) {
QueDatabase database = QueDatabase.getInstance(application);
queDao = database.queDao();
ques = queDao.getQuesAll();
}
public void insert(QueEnt queEnt) {
new InsertQAsync(queDao).execute(queEnt);
}
public void update(QueEnt queEnt) {
new UpdateQAsync(queDao).execute(queEnt);
}
public void delete(QueEnt queEnt) {
new DeleteQAsync(queDao).execute(queEnt);
}
public LiveData<List<QueEnt>> getQues() {
return ques;
}
private static class InsertQAsync extends AsyncTask<QueEnt, Void, Void> {
private QueDao queDao;
private InsertQAsync(QueDao queDao) {
this.queDao = queDao;
}
#Override
protected Void doInBackground(QueEnt... queEnts) {
queDao.insert(queEnts[0]);
return null;
}
}
private static class UpdateQAsync extends AsyncTask<QueEnt, Void, Void> {
private QueDao queDao;
private UpdateQAsync(QueDao queDao) {
this.queDao = queDao;
}
#Override
protected Void doInBackground(QueEnt... queEnts) {
queDao.update(queEnts[0]);
return null;
}
}
private static class DeleteQAsync extends AsyncTask<QueEnt, Void, Void> {
private QueDao queDao;
private DeleteQAsync(QueDao queDao) {
this.queDao = queDao;
}
#Override
protected Void doInBackground(QueEnt... queEnts) {
queDao.delete(queEnts[0]);
return null;
}
}
}
And QueDao :
#Dao
public interface QueDao {
#Insert
void insert(QueEnt queEnt);
#Update
void update(QueEnt queEnt);
#Delete
void delete(QueEnt queEnt);
#Query("SELECT * FROM questions LIMIT 30" )
LiveData<List<QueEnt>> getQuesAll();
}
And Database :
#Database(entities = {QueEnt.class}, version = 1)
public abstract class QueDatabase extends RoomDatabase {
public static QueDatabase instance;
public static synchronized QueDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context.getApplicationContext(), QueDatabase.class, "que_database")
.fallbackToDestructiveMigration()
.addCallback(roomCallback)
.build();
}
return instance;
}
public abstract QueDao queDao();
private static RoomDatabase.Callback roomCallback = new RoomDatabase.Callback() {
#Override
public void onCreate(#NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
new PopulateDbAsync(instance).execute();
}
};
private static class PopulateDbAsync extends AsyncTask<Void, Void, Void> {
private QueDao queDao;
private PopulateDbAsync(QueDatabase db) {
queDao = db.queDao();
}
#Override
protected Void doInBackground(Void... voids) {
queDao.insert(new QueEnt(91, 93, "Question 1?", " Answer 1", "Answer2 ", "Answer 3 ", " Answer4 ", 2, 2, 1,0,0,0,0,0,"","","","",""));
queDao.insert(new QueEnt(93, 95, "Question 2?", " Answer 1", "Answer2 ", "Answer 3 ", " Answer4 ", 3, 2, 4,0,0,0,0,0,"","","","",""));
return null;
}
}
}