Android: 如何使用观察器进行多个过滤条件?
Android: how do I use observer for multiple filter criteria?
我有一个 CardView 的 RecyclerView 列表。我希望用户能够通过单击不同的 TextViews 来根据 CardView 类型过滤列表。过滤器第一次正常工作。如果我单击 One TextView,列表将被过滤,视图仅显示 One 卡片。 backspace 将用户带回到完整列表。接下来,用户单击两个 TextView。这就是问题发生的地方,因为视图显示过滤后的一张卡列表,而不是预期的两张卡。所以观察者默认使用原始的单卡过滤器类型,而不是预期的双卡过滤器类型。
如果用户先选择Two card TextView,也会出现同样的错误。在这种情况下,只有两张卡片按预期显示在视图中。返回space returns 用户的完整列表。然后单击 One TextView 再次过滤列表,但显示两张卡片而不是预期的一张卡片。
我在这里错过了什么?我尝试在每次单击 TextView 后使用 removeObserver() 和 removeObservers() 但没有成功。我如何重新使用具有多个过滤条件的观察者,这样我就不必为每个过滤器设置单独的观察者,然后需要 ViewModel、Repository 和 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;
}
}
}
你的代码中有几个问题。
每次向适配器设置新数据后调用cardsAdapter.notifyDataSetChanged()
。
这个方法
public LiveData<List<Card>> getFilteredList(String cardType) {
if (filteredList == null) {
filteredList = new MutableLiveData<>();
loadFilteredCards(cardType);
}
return filteredList;
}
查看此代码。如果 filteredList
为空,则用数据填充此列表 - 这是您第一次检索数据时发生的情况。第二次尝试过滤 - 你只做 return filteredList;
- 因此你 return 与你在之前的过滤器中已经形成的列表相同。
- 你使用的 LiveData
非常不正确 - 它在你的代码中作为实际数据的一些奇怪的无用代理工作。
我建议这样做
// 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);
}
}
它可能需要一些额外的工作,但我希望你能做到。
编辑
如果你真的想使用 LiveData
只需像这样编辑 getFilteredList()
public LiveData<List<Card>> getFilteredList(String cardType) {
filteredList = new MutableLiveData<>();
loadFilteredCards(cardType);
return filteredList;
}
每次都重新加载 - 不要检查是否为空。
希望对您有所帮助。
阅读这篇精彩的文章:MediatorLiveData to the Rescue
您需要从您的角度观察多个来源。引用文章:
“去这里的方法叫做 MediatorLiveData:
LiveData 子类,它可以观察其他 LiveData 对象并对来自它们的 OnChanged 事件做出反应。”
我相信你会没事的,但是,如果你在生成 java 代码时遇到任何问题,请告诉我。
此外,检查一下也无妨:MediatorLiveData
我有一个 CardView 的 RecyclerView 列表。我希望用户能够通过单击不同的 TextViews 来根据 CardView 类型过滤列表。过滤器第一次正常工作。如果我单击 One TextView,列表将被过滤,视图仅显示 One 卡片。 backspace 将用户带回到完整列表。接下来,用户单击两个 TextView。这就是问题发生的地方,因为视图显示过滤后的一张卡列表,而不是预期的两张卡。所以观察者默认使用原始的单卡过滤器类型,而不是预期的双卡过滤器类型。
如果用户先选择Two card TextView,也会出现同样的错误。在这种情况下,只有两张卡片按预期显示在视图中。返回space returns 用户的完整列表。然后单击 One TextView 再次过滤列表,但显示两张卡片而不是预期的一张卡片。
我在这里错过了什么?我尝试在每次单击 TextView 后使用 removeObserver() 和 removeObservers() 但没有成功。我如何重新使用具有多个过滤条件的观察者,这样我就不必为每个过滤器设置单独的观察者,然后需要 ViewModel、Repository 和 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;
}
}
}
你的代码中有几个问题。
每次向适配器设置新数据后调用
cardsAdapter.notifyDataSetChanged()
。这个方法
public LiveData<List<Card>> getFilteredList(String cardType) {
if (filteredList == null) {
filteredList = new MutableLiveData<>();
loadFilteredCards(cardType);
}
return filteredList;
}
查看此代码。如果 filteredList
为空,则用数据填充此列表 - 这是您第一次检索数据时发生的情况。第二次尝试过滤 - 你只做 return filteredList;
- 因此你 return 与你在之前的过滤器中已经形成的列表相同。
- 你使用的 LiveData
非常不正确 - 它在你的代码中作为实际数据的一些奇怪的无用代理工作。
我建议这样做
// 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);
}
}
它可能需要一些额外的工作,但我希望你能做到。
编辑
如果你真的想使用 LiveData
只需像这样编辑 getFilteredList()
public LiveData<List<Card>> getFilteredList(String cardType) {
filteredList = new MutableLiveData<>();
loadFilteredCards(cardType);
return filteredList;
}
每次都重新加载 - 不要检查是否为空。
希望对您有所帮助。
阅读这篇精彩的文章:MediatorLiveData to the Rescue
您需要从您的角度观察多个来源。引用文章: “去这里的方法叫做 MediatorLiveData: LiveData 子类,它可以观察其他 LiveData 对象并对来自它们的 OnChanged 事件做出反应。” 我相信你会没事的,但是,如果你在生成 java 代码时遇到任何问题,请告诉我。
此外,检查一下也无妨:MediatorLiveData