Recyclerview 项目有时不会在应用程序启动时显示
Recyclerview items are sometimes not displayed on app launch
我有一个显示可启动应用程序列表的屏幕。该列表是通过 RxJava2 和 LiveData 的组合从 SharedPreferences
中获取的。具体来说,我在片段的 onStart
方法上观察到 LiveData<List<AppModel>>
。使用 RxJava2 成功获取此列表后,我使用 LiveData 使用列表更新 UI 并将其设置为我的 RecyclerView。
但是,我注意到有时我第一次启动应用程序时,应用程序列表已成功获取,但项目没有显示在 UI 上。这是我查看此行为的程序:
- 从主屏幕打开应用程序
- 如果项目显示成功,关闭应用程序
- 从最近列表中删除应用程序
- 启动应用程序并再次执行程序,直到项目不再显示。
出于好奇,我将观察 LiveData<List<AppModel>>
的代码移动到 onCreateView
,现在每次启动应用程序时,项目都会成功显示。另外,bug只发生在API 22,我在API 27上测试过,bug没有出现。有人知道为什么会这样吗?
以下是存在项目未显示错误的代码:
1) FavoritesFragment.java(保存的应用程序列表通过 RecyclerView 显示):
public class FavoritesFragment extends Fragment {
public static final String TAG = FavoritesFragment.class.getSimpleName();
private FaveListAdapter faveListAdapter;
FragmentFavoritesBinding binding;
private List<AppModel> faveList = new ArrayList<>();
@Inject
public ViewModelFactory viewModelFactory;
private FavoritesViewModel viewModel;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
Injector.getViewModelComponent().inject(this);
super.onCreate(savedInstanceState);
viewModel = ViewModelProviders.of(this, viewModelFactory).get(FavoritesViewModel.class);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
binding = FragmentFavoritesBinding.inflate(inflater, container, false);
binding.button.setOnClickListener((v ->
navController.navigate(R.id.action_favorites_dest_to_app_list_dest)));
faveListAdapter = new FaveListAdapter(this::launchApp);
faveListAdapter.setAppList(faveList);
faveListAdapter.setOnDeleteItemListener(list -> {
faveList = list;
viewModel.saveFaveApps(faveList).observe(getViewLifecycleOwner(), this::handleSaveStatus);
updateRecyclerView();
});
binding.rvNav.setLayoutManager(new LinearLayoutManager(requireContext()));
binding.rvNav.setAdapter(faveListAdapter);
Log.d(TAG, "onCreateView: done initial RV setup");
updateRecyclerView();
return binding.getRoot();
}
@Override
public void onStart() {
super.onStart();
viewModel.loadFaveAppList().observe(this, list -> {
faveList = list;
faveListAdapter.swapItems(list);
updateRecyclerView();
});
}
private void updateRecyclerView() {
Log.d(TAG, "updateRecyclerView: start");
if(faveList.isEmpty()) {
binding.button.setVisibility(View.VISIBLE);
binding.frameFav.setVisibility(View.GONE);
} else {
binding.button.setVisibility(View.GONE);
binding.frameFav.setVisibility(View.VISIBLE);
}
}
private void launchApp(String packageName) {
// launch selected app
}
private void handleSaveStatus(SaveStatus saveStatus) {
// change UI/navigate to other screens depending on status
}
}
}
2) FavoritesViewModel.java(我使用 RxJava2 从存储库对象获取列表并通过 LiveData 更新 UI)
public class FavoritesViewModel extends ViewModel {
private final PreferenceRepository preferenceRepository;
private CompositeDisposable compositeDisposable;
private List<String> favePackageNameList = new ArrayList<>();
@Inject
public FavoritesViewModel(PreferenceRepository preferenceRepository, DataRepository dataRepository) {
this.preferenceRepository = preferenceRepository;
this.dataRepository = dataRepository;
compositeDisposable = new CompositeDisposable();
}
public LiveData<List<AppModel>> loadFaveAppList() {
MutableLiveData<List<AppModel>> listData = new MutableLiveData<>();
compositeDisposable.add(dataRepository.loadFavesFromPrefs()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(listData::setValue, Throwable::printStackTrace));
return listData;
}
public LiveData<SaveStatus> saveFaveApps(List<AppModel> faveList) {
MutableLiveData<SaveStatus> saveStatus = new MutableLiveData<>();
compositeDisposable.add(dataRepository.saveFaveAppListToPrefs(faveList)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(disposable -> saveStatus.setValue(SaveStatus.SAVING))
.subscribe(() -> saveStatus.setValue(SaveStatus.DONE),
error -> {
error.printStackTrace();
saveStatus.setValue(SaveStatus.ERROR);
})
);
return saveStatus;
}
}
3)FavoritesAdapter.java(实现上下文操作栏逻辑的RecyclerView适配器,也使用DiffUtils)
public class FaveListAdapter extends RecyclerView.Adapter<FaveListAdapter.ViewHolder> {
public interface FaveItemClickListener {
void onItemClick(String packageName);
}
public interface DeleteItemListener {
void onDeleteClick(List<AppModel> newAppList);
}
private List<AppModel> appList = new ArrayList<>();
private FaveItemClickListener onFaveItemClickListener;
private DeleteItemListener onDeleteItemListener;
private boolean multiSelect = false;
private List<AppModel> selectedItems = new ArrayList<>();
private ActionMode.Callback actionModeCallbacks = new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
multiSelect = true;
menu.add("Delete");
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
for(AppModel app : selectedItems) {
appList.remove(app);
}
if(onDeleteItemListener != null) {
onDeleteItemListener.onDeleteClick(appList);
}
mode.finish();
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
multiSelect = false;
selectedItems.clear();
notifyDataSetChanged();
}
};
public FaveListAdapter(FaveItemClickListener onFaveItemClickListener) {
this.onFaveItemClickListener = onFaveItemClickListener;
}
public void setAppList(List<AppModel> appList) {
this.appList = appList;
notifyDataSetChanged();
}
public void setOnDeleteItemListener(DeleteItemListener onDeleteItemListener) {
this.onDeleteItemListener = onDeleteItemListener;
}
class ViewHolder extends RecyclerView.ViewHolder {
private final ImageView appIcon;
private final TextView appLabel;
private final ConstraintLayout itemLayout;
public ViewHolder(@NonNull View itemView) {
super(itemView);
appIcon = itemView.findViewById(R.id.app_icon);
appLabel = itemView.findViewById(R.id.app_label);
itemLayout = itemView.findViewById(R.id.item_layout);
}
private void selectItem(AppModel app) {
if(multiSelect) {
if(selectedItems.contains(app)) {
selectedItems.remove(app);
itemLayout.setBackgroundColor(Color.WHITE);
} else {
selectedItems.add(app);
itemLayout.setBackgroundColor(Color.LTGRAY);
}
}
}
private void bind(AppModel app, int i) {
appIcon.setImageDrawable(app.getLauncherIcon());
appLabel.setText(app.getAppLabel());
if(selectedItems.contains(app)) {
itemLayout.setBackgroundColor(Color.LTGRAY);
} else {
itemLayout.setBackgroundColor(Color.WHITE);
}
this.itemView.setOnClickListener(v ->{
if(multiSelect) {
selectItem(app);
} else {
onFaveItemClickListener.onItemClick(appList.get(i).getPackageName());
}
});
this.itemView.setOnLongClickListener(v -> {
((AppCompatActivity) v.getContext()).startSupportActionMode(actionModeCallbacks);
selectItem(app);
return true;
});
}
}
@NonNull
@Override
public FaveListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_fave, parent, false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull FaveListAdapter.ViewHolder holder, int position) {
holder.bind(appList.get(position), position);
}
@Override
public int getItemCount() {
return appList.size();
}
public void swapItems(List<AppModel> apps) {
final AppModelDiffCallback diffCallback = new AppModelDiffCallback(this.appList, apps);
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
this.appList.clear();
this.appList.addAll(apps);
diffResult.dispatchUpdatesTo(this);
}
}
片段和片段的 viewLifecycleOwner 具有不同的生命周期。 ViewLifecycleOwner 在onCreateView 中订阅,在onDestroyView 中取消订阅。 Fragment 的生命周期在 onCreate 中订阅并在 onDestroy
中取消订阅
将此代码移至 onCreateView()
viewModel.loadFaveAppList().observe(getViewLifecycleOwner, list -> { <-- change this
faveList = list;
faveListAdapter.swapItems(list);
updateRecyclerView();
});
https://proandroiddev.com/5-common-mistakes-when-using-architecture-components-403e9899f4cb
我有一个显示可启动应用程序列表的屏幕。该列表是通过 RxJava2 和 LiveData 的组合从 SharedPreferences
中获取的。具体来说,我在片段的 onStart
方法上观察到 LiveData<List<AppModel>>
。使用 RxJava2 成功获取此列表后,我使用 LiveData 使用列表更新 UI 并将其设置为我的 RecyclerView。
但是,我注意到有时我第一次启动应用程序时,应用程序列表已成功获取,但项目没有显示在 UI 上。这是我查看此行为的程序:
- 从主屏幕打开应用程序
- 如果项目显示成功,关闭应用程序
- 从最近列表中删除应用程序
- 启动应用程序并再次执行程序,直到项目不再显示。
出于好奇,我将观察 LiveData<List<AppModel>>
的代码移动到 onCreateView
,现在每次启动应用程序时,项目都会成功显示。另外,bug只发生在API 22,我在API 27上测试过,bug没有出现。有人知道为什么会这样吗?
以下是存在项目未显示错误的代码:
1) FavoritesFragment.java(保存的应用程序列表通过 RecyclerView 显示):
public class FavoritesFragment extends Fragment {
public static final String TAG = FavoritesFragment.class.getSimpleName();
private FaveListAdapter faveListAdapter;
FragmentFavoritesBinding binding;
private List<AppModel> faveList = new ArrayList<>();
@Inject
public ViewModelFactory viewModelFactory;
private FavoritesViewModel viewModel;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
Injector.getViewModelComponent().inject(this);
super.onCreate(savedInstanceState);
viewModel = ViewModelProviders.of(this, viewModelFactory).get(FavoritesViewModel.class);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
binding = FragmentFavoritesBinding.inflate(inflater, container, false);
binding.button.setOnClickListener((v ->
navController.navigate(R.id.action_favorites_dest_to_app_list_dest)));
faveListAdapter = new FaveListAdapter(this::launchApp);
faveListAdapter.setAppList(faveList);
faveListAdapter.setOnDeleteItemListener(list -> {
faveList = list;
viewModel.saveFaveApps(faveList).observe(getViewLifecycleOwner(), this::handleSaveStatus);
updateRecyclerView();
});
binding.rvNav.setLayoutManager(new LinearLayoutManager(requireContext()));
binding.rvNav.setAdapter(faveListAdapter);
Log.d(TAG, "onCreateView: done initial RV setup");
updateRecyclerView();
return binding.getRoot();
}
@Override
public void onStart() {
super.onStart();
viewModel.loadFaveAppList().observe(this, list -> {
faveList = list;
faveListAdapter.swapItems(list);
updateRecyclerView();
});
}
private void updateRecyclerView() {
Log.d(TAG, "updateRecyclerView: start");
if(faveList.isEmpty()) {
binding.button.setVisibility(View.VISIBLE);
binding.frameFav.setVisibility(View.GONE);
} else {
binding.button.setVisibility(View.GONE);
binding.frameFav.setVisibility(View.VISIBLE);
}
}
private void launchApp(String packageName) {
// launch selected app
}
private void handleSaveStatus(SaveStatus saveStatus) {
// change UI/navigate to other screens depending on status
}
}
}
2) FavoritesViewModel.java(我使用 RxJava2 从存储库对象获取列表并通过 LiveData 更新 UI)
public class FavoritesViewModel extends ViewModel {
private final PreferenceRepository preferenceRepository;
private CompositeDisposable compositeDisposable;
private List<String> favePackageNameList = new ArrayList<>();
@Inject
public FavoritesViewModel(PreferenceRepository preferenceRepository, DataRepository dataRepository) {
this.preferenceRepository = preferenceRepository;
this.dataRepository = dataRepository;
compositeDisposable = new CompositeDisposable();
}
public LiveData<List<AppModel>> loadFaveAppList() {
MutableLiveData<List<AppModel>> listData = new MutableLiveData<>();
compositeDisposable.add(dataRepository.loadFavesFromPrefs()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(listData::setValue, Throwable::printStackTrace));
return listData;
}
public LiveData<SaveStatus> saveFaveApps(List<AppModel> faveList) {
MutableLiveData<SaveStatus> saveStatus = new MutableLiveData<>();
compositeDisposable.add(dataRepository.saveFaveAppListToPrefs(faveList)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(disposable -> saveStatus.setValue(SaveStatus.SAVING))
.subscribe(() -> saveStatus.setValue(SaveStatus.DONE),
error -> {
error.printStackTrace();
saveStatus.setValue(SaveStatus.ERROR);
})
);
return saveStatus;
}
}
3)FavoritesAdapter.java(实现上下文操作栏逻辑的RecyclerView适配器,也使用DiffUtils)
public class FaveListAdapter extends RecyclerView.Adapter<FaveListAdapter.ViewHolder> {
public interface FaveItemClickListener {
void onItemClick(String packageName);
}
public interface DeleteItemListener {
void onDeleteClick(List<AppModel> newAppList);
}
private List<AppModel> appList = new ArrayList<>();
private FaveItemClickListener onFaveItemClickListener;
private DeleteItemListener onDeleteItemListener;
private boolean multiSelect = false;
private List<AppModel> selectedItems = new ArrayList<>();
private ActionMode.Callback actionModeCallbacks = new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
multiSelect = true;
menu.add("Delete");
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
for(AppModel app : selectedItems) {
appList.remove(app);
}
if(onDeleteItemListener != null) {
onDeleteItemListener.onDeleteClick(appList);
}
mode.finish();
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
multiSelect = false;
selectedItems.clear();
notifyDataSetChanged();
}
};
public FaveListAdapter(FaveItemClickListener onFaveItemClickListener) {
this.onFaveItemClickListener = onFaveItemClickListener;
}
public void setAppList(List<AppModel> appList) {
this.appList = appList;
notifyDataSetChanged();
}
public void setOnDeleteItemListener(DeleteItemListener onDeleteItemListener) {
this.onDeleteItemListener = onDeleteItemListener;
}
class ViewHolder extends RecyclerView.ViewHolder {
private final ImageView appIcon;
private final TextView appLabel;
private final ConstraintLayout itemLayout;
public ViewHolder(@NonNull View itemView) {
super(itemView);
appIcon = itemView.findViewById(R.id.app_icon);
appLabel = itemView.findViewById(R.id.app_label);
itemLayout = itemView.findViewById(R.id.item_layout);
}
private void selectItem(AppModel app) {
if(multiSelect) {
if(selectedItems.contains(app)) {
selectedItems.remove(app);
itemLayout.setBackgroundColor(Color.WHITE);
} else {
selectedItems.add(app);
itemLayout.setBackgroundColor(Color.LTGRAY);
}
}
}
private void bind(AppModel app, int i) {
appIcon.setImageDrawable(app.getLauncherIcon());
appLabel.setText(app.getAppLabel());
if(selectedItems.contains(app)) {
itemLayout.setBackgroundColor(Color.LTGRAY);
} else {
itemLayout.setBackgroundColor(Color.WHITE);
}
this.itemView.setOnClickListener(v ->{
if(multiSelect) {
selectItem(app);
} else {
onFaveItemClickListener.onItemClick(appList.get(i).getPackageName());
}
});
this.itemView.setOnLongClickListener(v -> {
((AppCompatActivity) v.getContext()).startSupportActionMode(actionModeCallbacks);
selectItem(app);
return true;
});
}
}
@NonNull
@Override
public FaveListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_fave, parent, false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull FaveListAdapter.ViewHolder holder, int position) {
holder.bind(appList.get(position), position);
}
@Override
public int getItemCount() {
return appList.size();
}
public void swapItems(List<AppModel> apps) {
final AppModelDiffCallback diffCallback = new AppModelDiffCallback(this.appList, apps);
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
this.appList.clear();
this.appList.addAll(apps);
diffResult.dispatchUpdatesTo(this);
}
}
片段和片段的 viewLifecycleOwner 具有不同的生命周期。 ViewLifecycleOwner 在onCreateView 中订阅,在onDestroyView 中取消订阅。 Fragment 的生命周期在 onCreate 中订阅并在 onDestroy
中取消订阅将此代码移至 onCreateView()
viewModel.loadFaveAppList().observe(getViewLifecycleOwner, list -> { <-- change this
faveList = list;
faveListAdapter.swapItems(list);
updateRecyclerView();
});
https://proandroiddev.com/5-common-mistakes-when-using-architecture-components-403e9899f4cb