Reputation: 1649
I have a RecyclerView List of CardViews. I would like the user to be able to filter the List based on CardView types by clicking on different TextViews for the types. The filter works correctly the first time. If I click on the One TextView the List is filtered and the View shows only the One cards. A backspace brings the user back to the full List. Next, the user clicks on the Two TextView. Here is where the problem occurs, as the View shows the filtered List of One cards rather than the epected Two cards. So the observer is defaulting to the original One card filter type rather than the expected Two card filter type.
The same error occurs if the user chooses the Two card TextView first. In this case, only the Two cards are shown in the View as expected. The back space returns the user to the full List. Then a click on the One TextView filters the List again but shows the Two cards rather than the expected One cards.
What am I missing here? I tried to use removeObserver() and removeObservers() after each click on the TextView but no luck. How do I re-use an observer with multiple filter criteria so that I don't have to set up individual observers for each filter that would then require individual methods in the ViewModel, Repository and Dao?
public class MainActivity extends AppCompatActivity {
// for filtering card type
final String type0 = "OneCards";
final String type1 = "TwoCards";
private List<Card> filteredModelList0 = null;
private List<Card> filteredModelList1 = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewModel = new ViewModelProvider(this).get(ViewModel.class);
}
public void onClickFilterList(View view) {
// set up an AlertDialog for the user to select a filter
final AlertDialog.Builder alertDialogFilter = new AlertDialog.Builder(MainActivity.this);
LayoutInflater inflaterFilter = getLayoutInflater();
final ViewGroup nullParent = null;
final View dialogLayoutFilter = inflaterFilter.inflate(R.layout.filter_main_aldialog, nullParent);
alertDialogFilter.setView(dialogLayoutFilter);
final AlertDialog dialogFilter = alertDialogFilter.create();
dialogFilter.show();
TextView allOnes = dialogLayoutFilter.findViewById(R.id.AllOnes);
TextView allTwos = dialogLayoutFilter.findViewById(R.id.AllTwos);
allOnes.setOnClickListener(v -> {
mViewModel.getFilteredList(type0).observe(this, filterList -> {
filteredModelList0 = filterList;
if (filterList.size() == 0) {
Toast.makeText(MainActivity.this, "There are no 'One' cards", Toast.LENGTH_SHORT).show();
filteredModelList0 = null;
} else {
cardsAdapter.setCardList(filteredModelList0);
Toast.makeText(MainActivity.this, "There are 'One' cards", Toast.LENGTH_SHORT).show();
}
});
dialogFilter.dismiss();
});
allTwos.setOnClickListener(v -> {
mViewModel.getFilteredList(type1).observe(this, filterList -> {
filteredModelList1 = filterList;
if (filterList.size() == 0) {
Toast.makeText(MainActivity.this, "There are no 'Two' cards", Toast.LENGTH_SHORT).show();
filteredModelList1 = null;
} else {
cardsAdapter.setCardList(filteredModelList1);
Toast.makeText(MainActivity.this, "There are 'Two' cards", Toast.LENGTH_SHORT).show();
}
});
dialogFilter.dismiss();
});
}
ViewModel
public class ViewModel extends AndroidViewModel {
…
private MutableLiveData<List<Card>> filteredList = null;
public LiveData<List<Card>> getFilteredList(String cardType) {
if (filteredList == null) {
filteredList = new MutableLiveData<>();
loadFilteredCards(cardType);
}
return filteredList;
}
public void loadFilteredCards(String cardType){
filteredList.postValue(repository.getFilteredCards(cardType));
}
Adapter
…
public class Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public List<Card> mListItems;
…
public void setCardList(List<Card> newList) {
if (mListItems != null) {
PostDiffCallback postDiffCallback = new PostDiffCallback(mListItems, newList);
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(postDiffCallback);
mListItems.clear();
mListItems.addAll(newList);
diffResult.dispatchUpdatesTo(this);
} else { // first initialization.
mListItems = newList;
}
}
}
Upvotes: 4
Views: 1582
Reputation: 2883
Read this amazing article: MediatorLiveData to the Rescue
You need to observe several sources from your view. Quoting the article: "The way to go here is called MediatorLiveData: LiveData subclass which may observe other LiveData objects and react on OnChanged events from them." I'm sure you'd be fine, but, if you had any problem producing the java code let me know.
Also, it wouldn't hurt to check this out too: MediatorLiveData
Upvotes: 1
Reputation: 16699
There are several problems in your code.
call cardsAdapter.notifyDataSetChanged()
each time after you set new data to adapter.
this method
public LiveData<List<Card>> getFilteredList(String cardType) {
if (filteredList == null) {
filteredList = new MutableLiveData<>();
loadFilteredCards(cardType);
}
return filteredList;
}
Look into this code. If filteredList
is null you fill this list with data - which is happed the first time you retrieve data. The second time you try to filter - you do only return filteredList;
- thus you return the same list you've already formed on your previous filter.
- You are using LiveData
quite incorrect - it works in your code as some weird useless proxy for the actual data.
I would suggest to do it like this
// For listeners
...
allOnes.setOnClickListener(v -> {
filteredModelList0 = mViewModel.getFilteredList(type0);
...
cardsAdapter.setCardList(filteredModelList0);
cardsAdapter.notifyDataSetChanged()
...
});
allTwos.setOnClickListener(v -> {
filteredModelList1 = mViewModel.getFilteredList(type1);
...
cardsAdapter.setCardList(filteredModelList1);
cardsAdapter.notifyDataSetChanged()
...
});
...
//For ViewModel
public class ViewModel extends AndroidViewModel {
public LiveData<List<Card>> getFilteredList(String cardType) {
return repository.getFilteredCards(cardType);
}
}
It may require some additional work but I hope you'll manage to do it.
If you really want to use LiveData
just edit getFilteredList()
like this
public LiveData<List<Card>> getFilteredList(String cardType) {
filteredList = new MutableLiveData<>();
loadFilteredCards(cardType);
return filteredList;
}
Reload it each time - don't check for null.
Hope it helps.
Upvotes: 2