在 LiveData 中显示来自 Observer 的数据滞后
Lag in displaying data from Oberver on LiveData
我正在使用填充 RecyclerView.Adapter 的 ViewModel,并从我的 Room 数据库中的 LiveData 加载数据。问题是我的显示总是空白,并且使用 Dao(用于调试,在主线程上)检查显示数据检索得很好。 (因此数据库中有数据)。
问题是我的 LiveData 上的 Observer 总是 returns 空值(或没有数据),我最终不得不至少刷新一次片段(通过移动和返回)才能看到任何东西- 即使是我放入数据库中进行测试的微薄记录。
重新启动应用程序或片段意味着空白屏幕和几次刷新,然后我才看到任何奇怪的东西,因为数据已经存在。
我不知道如何让它或多或少实时地向我显示数据。有人可以帮忙吗?
在此共享 DAO、ViewModel 和 Fragment 代码。
片段
... import libs and set up variables ...
private HouseCallAdapter houseCallAdapter;
private RecyclerView recyclerView;
private TextView emptyView;
RevivDatabase revivDatabase;
private LiveData<List<HouseCall>> liveHousecalls;
private List<HouseCall> houseCalls;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_reviv_housecall_request_list, container, false);
Bundle arguments = getArguments();
String action = arguments.getString("data");
revivDatabase = RevivDatabase.getDatabase(getActivity().getApplicationContext());
emptyView = view.findViewById(R.id.txtNoData);
recyclerView = view.findViewById(R.id.hcrecyclerView);
viewModel = ((Reviv) getActivity()).getViewModel();
if(liveHousecalls == null) {
liveHousecalls = new MutableLiveData<List<HouseCall>>();
}
houseCallAdapter = new HouseCallAdapter(getContext(), apikey, false, false);
liveHousecalls = viewModel.getOpenHousecalls();
// this is to test if there is actually any data retreived
// calling on main thread. Lose this code later.
houseCalls = revivDatabase.revivDao().getHousecallsByStatus(action);
break;
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity().getApplicationContext(), LinearLayoutManager.VERTICAL, false));
houseCallAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
checkEmpty();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
checkEmpty();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
checkEmpty();
}
void checkEmpty() {
//emptyView.setText (R.string.no_data_available);
emptyView.setVisibility(houseCallAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
recyclerView.setVisibility (houseCallAdapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
}
});
houseCallAdapter.setData(houseCalls);
houseCallAdapter.notifyDataSetChanged();
liveHousecalls.observe(getActivity(), new Observer<List<HouseCall>>() {
@Override
public void onChanged(@Nullable List<HouseCall> houseCalls) {
if(houseCalls != null) {
houseCallAdapter.setData(houseCalls);
houseCallAdapter.notifyDataSetChanged();
}
}
});
recyclerView.setItemAnimator (new DefaultItemAnimator());
recyclerView.setAdapter(houseCallAdapter);
emptyView.setVisibility(houseCallAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
return view;
}
ViewModel
private LiveData<List<HouseCall>> housecallList;
private LiveData<List<HouseCall>> openHousecalls, confirmedHousecalls, closedHousecalls, missedHousecalls, userCancelledHousecalls, respCancelledHousecalls;
private LiveData<List<Incident>> incidentList, openIncidents;
private LiveData<List<Incident>> closedIncidents, usercancelIncidents, respcancelIncidents;
private LiveData<Incident> liveIncident;
private RevivDatabase database;
Context context;
/////////////////////////////////////////////////////////
// CONSTRUCTOR
/////////////////////////////////////////////////////////
public RevivViewModel(Application application) {
super(application);
//////////////////////////////////////////////////////////////////////////
// //
// DANGER WILL ROBINSON //
// Storing context in ViewModel is Not A Good Idea (TM) //
context = application.getApplicationContext(); //
// //
//////////////////////////////////////////////////////////////////////////
database = RevivDatabase.getDatabase(application);
}
/////////////////////////////////////////////////////////
// GETTERS AND SETTERS
/////////////////////////////////////////////////////////
// Housecalls
public LiveData<List<HouseCall>> getHousecallList() {
if (housecallList == null) {
housecallList = new MutableLiveData<>();
loadHousecalls();
}
return housecallList;
}
public LiveData<List<HouseCall>> getOpenHousecalls() {
if (openHousecalls == null) {
openHousecalls = new MutableLiveData<>();
loadOpenHousecalls();
}
return openHousecalls;
}
/////////////////////////////////////////////////////////
// TRIGGER REFRESH FROM VIEWMODEL
/////////////////////////////////////////////////////////
// TBD
/////////////////////////////////////////////////////////
// EXTERNAL CALLS - REFRESH FROM DB
/////////////////////////////////////////////////////////
// Methods to accept/cancel incidents and housecalls
public void loadHousecalls(){
class OneShotTask implements Runnable {
OneShotTask() {
}
public void run() {
housecallList = database.revivDao().getAllLiveHousecalls();
//housecallList.postValue(hc);
}
}
Thread t = new Thread(new OneShotTask());
t.start();
}
public void loadOpenHousecalls(){
class OneShotTask implements Runnable {
OneShotTask() {
}
public void run() {
openHousecalls = database.revivDao().getLiveHousecallsByStatus("open");
}
}
Thread t = new Thread(new OneShotTask());
t.start();
}
}
DAO接口
public interface RevivDaoInterface {
// Housecalls
... numerous insert, delete and update calls ...
@Query("SELECT * FROM housecalls WHERE housecallid = :housecallid")
public HouseCall getHousecallById(String housecallid);
@Query("SELECT * FROM housecalls WHERE status = :status")
public List<HouseCall> getHousecallsByStatus(String status);
@Update(onConflict = OnConflictStrategy.IGNORE)
void updateHousecall(HouseCall houseCall);
@Query("SELECT * FROM housecalls WHERE status = \'open\'")
public LiveData<List<HouseCall>> getOpenHousecalls();
@Query("SELECT * FROM housecalls WHERE status = :status")
public LiveData<List<HouseCall>> getLiveHousecallsByStatus(String status);
@Query("SELECT * FROM housecalls")
public List<HouseCall> getAllHousecalls();
}
DAO
imports
@Dao
public abstract class RevivDao implements RevivDaoInterface {
@Transaction
public void upsert(HouseCall houseCall){
try {
this.insert(houseCall);
} catch (SQLiteConstraintException exception) {
this.update(houseCall);
Log.e(TAG, "upsert: ", exception);
}
}
@Transaction
public void upsert(List<HouseCall> houseCall){
for(HouseCall hc : houseCall) {
try {
this.insert(hc);
} catch (SQLiteConstraintException exception) {
this.update(hc);
Log.e(TAG, "upsert: ", exception);
}
}
}
}
感谢@pskink,我找到了解决将数据更新到我的 ViewModel 的延迟的方法。
为了解决这个问题,我不得不实现 PagedListAdapter。
在 build.gradle(模块)文件中
implementation 'android.arch.paging:runtime:1.0.1'
在 DAO 中
@Query("SELECT * from housecalls where status = :status")
public abstract DataSource.Factory<Integer, HouseCall> getHousecallPagesByStatus(String status);
在视图模型中
//declare a LiveData of PagedList
LiveData<PagedList<HouseCall>> openhousecallPages;
// Define Configuration for Paged List
Config pagedListConfig = (new PagedList.Config.Builder()).setEnablePlaceholders(true)
.setPrefetchDistance(10)
.setPageSize(20).build();
// Function to access the data
public LiveData<PagedList<HouseCall>> getOpenhousecallPages(){
openhousecallPages = new LivePagedListBuilder<>(database.revivDao().getHousecallPagesByStatus("open"),
pagedListConfig).build();
return openhousecallPages;
}
设置 PagedListAdapter
package packagename;
import static android.content.ContentValues.TAG;
public class HouseCallPagedAdapter extends PagedListAdapter<HouseCall, HouseCallViewHolder>{
protected HouseCallPagedAdapter(@NonNull DiffUtil.ItemCallback<HouseCall> diffCallback) {
super(diffCallback);
}
public HouseCallPagedAdapter(@NonNull DiffUtil.ItemCallback diffcallback){
super(diffcallback);
}
@NonNull
@Override
public HouseCallViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int i) {
return new HouseCallViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.card_housecall_request, parent, false),parent.getContext());
}
@Override
public void onBindViewHolder(@NonNull in.portmanteau.reviv.Adapters.HouseCallViewHolder holder, int i) {
holder.bindTo(getItem(i));
}
}
定义一个具有以下结构的ViewHolder
public class HouseCallViewHolder extends RecyclerView.ViewHolder{
// declare values, elements, etc
public HouseCallViewHolder(View itemView, Context mContext) {
super(itemView);
// set up UI elements
}
void bindTo(final HouseCall houseCall){
this.houseCall = houseCall;
//populate values, set onClickListeners, etc.
}
}
最后,在您的Activity/Fragment!
中使用适配器
// Implement an DiffUtil.ItemCallback
private DiffUtil.ItemCallback<HouseCall> diffCallback = new DiffUtil.ItemCallback<HouseCall>() {
@Override
public boolean areItemsTheSame(@NonNull HouseCall houseCall, @NonNull HouseCall newhouseCall) {
return houseCall.getHousecallid().equalsIgnoreCase(newhouseCall.getHousecallid()) ;
}
@Override
public boolean areContentsTheSame(@NonNull HouseCall houseCall, @NonNull HouseCall newhouseCall) {
return houseCall.isTheSame(newhouseCall);
}
};
HouseCallPagedAdapter houseCallPagedAdapter = new HouseCallPagedAdapter(diffCallback);
viewModel.getOpenhousecallPages().observe(this, houseCallPagedAdapter::submitList);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity().getApplicationContext(), LinearLayoutManager.VERTICAL, false));
recyclerView.setItemAnimator (new DefaultItemAnimator());
recyclerView.setAdapter(houseCallPagedAdapter);
我正在使用填充 RecyclerView.Adapter 的 ViewModel,并从我的 Room 数据库中的 LiveData 加载数据。问题是我的显示总是空白,并且使用 Dao(用于调试,在主线程上)检查显示数据检索得很好。 (因此数据库中有数据)。
问题是我的 LiveData 上的 Observer 总是 returns 空值(或没有数据),我最终不得不至少刷新一次片段(通过移动和返回)才能看到任何东西- 即使是我放入数据库中进行测试的微薄记录。
重新启动应用程序或片段意味着空白屏幕和几次刷新,然后我才看到任何奇怪的东西,因为数据已经存在。
我不知道如何让它或多或少实时地向我显示数据。有人可以帮忙吗?
在此共享 DAO、ViewModel 和 Fragment 代码。
片段
... import libs and set up variables ...
private HouseCallAdapter houseCallAdapter;
private RecyclerView recyclerView;
private TextView emptyView;
RevivDatabase revivDatabase;
private LiveData<List<HouseCall>> liveHousecalls;
private List<HouseCall> houseCalls;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_reviv_housecall_request_list, container, false);
Bundle arguments = getArguments();
String action = arguments.getString("data");
revivDatabase = RevivDatabase.getDatabase(getActivity().getApplicationContext());
emptyView = view.findViewById(R.id.txtNoData);
recyclerView = view.findViewById(R.id.hcrecyclerView);
viewModel = ((Reviv) getActivity()).getViewModel();
if(liveHousecalls == null) {
liveHousecalls = new MutableLiveData<List<HouseCall>>();
}
houseCallAdapter = new HouseCallAdapter(getContext(), apikey, false, false);
liveHousecalls = viewModel.getOpenHousecalls();
// this is to test if there is actually any data retreived
// calling on main thread. Lose this code later.
houseCalls = revivDatabase.revivDao().getHousecallsByStatus(action);
break;
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity().getApplicationContext(), LinearLayoutManager.VERTICAL, false));
houseCallAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
checkEmpty();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
checkEmpty();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
checkEmpty();
}
void checkEmpty() {
//emptyView.setText (R.string.no_data_available);
emptyView.setVisibility(houseCallAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
recyclerView.setVisibility (houseCallAdapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
}
});
houseCallAdapter.setData(houseCalls);
houseCallAdapter.notifyDataSetChanged();
liveHousecalls.observe(getActivity(), new Observer<List<HouseCall>>() {
@Override
public void onChanged(@Nullable List<HouseCall> houseCalls) {
if(houseCalls != null) {
houseCallAdapter.setData(houseCalls);
houseCallAdapter.notifyDataSetChanged();
}
}
});
recyclerView.setItemAnimator (new DefaultItemAnimator());
recyclerView.setAdapter(houseCallAdapter);
emptyView.setVisibility(houseCallAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
return view;
}
ViewModel
private LiveData<List<HouseCall>> housecallList;
private LiveData<List<HouseCall>> openHousecalls, confirmedHousecalls, closedHousecalls, missedHousecalls, userCancelledHousecalls, respCancelledHousecalls;
private LiveData<List<Incident>> incidentList, openIncidents;
private LiveData<List<Incident>> closedIncidents, usercancelIncidents, respcancelIncidents;
private LiveData<Incident> liveIncident;
private RevivDatabase database;
Context context;
/////////////////////////////////////////////////////////
// CONSTRUCTOR
/////////////////////////////////////////////////////////
public RevivViewModel(Application application) {
super(application);
//////////////////////////////////////////////////////////////////////////
// //
// DANGER WILL ROBINSON //
// Storing context in ViewModel is Not A Good Idea (TM) //
context = application.getApplicationContext(); //
// //
//////////////////////////////////////////////////////////////////////////
database = RevivDatabase.getDatabase(application);
}
/////////////////////////////////////////////////////////
// GETTERS AND SETTERS
/////////////////////////////////////////////////////////
// Housecalls
public LiveData<List<HouseCall>> getHousecallList() {
if (housecallList == null) {
housecallList = new MutableLiveData<>();
loadHousecalls();
}
return housecallList;
}
public LiveData<List<HouseCall>> getOpenHousecalls() {
if (openHousecalls == null) {
openHousecalls = new MutableLiveData<>();
loadOpenHousecalls();
}
return openHousecalls;
}
/////////////////////////////////////////////////////////
// TRIGGER REFRESH FROM VIEWMODEL
/////////////////////////////////////////////////////////
// TBD
/////////////////////////////////////////////////////////
// EXTERNAL CALLS - REFRESH FROM DB
/////////////////////////////////////////////////////////
// Methods to accept/cancel incidents and housecalls
public void loadHousecalls(){
class OneShotTask implements Runnable {
OneShotTask() {
}
public void run() {
housecallList = database.revivDao().getAllLiveHousecalls();
//housecallList.postValue(hc);
}
}
Thread t = new Thread(new OneShotTask());
t.start();
}
public void loadOpenHousecalls(){
class OneShotTask implements Runnable {
OneShotTask() {
}
public void run() {
openHousecalls = database.revivDao().getLiveHousecallsByStatus("open");
}
}
Thread t = new Thread(new OneShotTask());
t.start();
}
}
DAO接口
public interface RevivDaoInterface {
// Housecalls
... numerous insert, delete and update calls ...
@Query("SELECT * FROM housecalls WHERE housecallid = :housecallid")
public HouseCall getHousecallById(String housecallid);
@Query("SELECT * FROM housecalls WHERE status = :status")
public List<HouseCall> getHousecallsByStatus(String status);
@Update(onConflict = OnConflictStrategy.IGNORE)
void updateHousecall(HouseCall houseCall);
@Query("SELECT * FROM housecalls WHERE status = \'open\'")
public LiveData<List<HouseCall>> getOpenHousecalls();
@Query("SELECT * FROM housecalls WHERE status = :status")
public LiveData<List<HouseCall>> getLiveHousecallsByStatus(String status);
@Query("SELECT * FROM housecalls")
public List<HouseCall> getAllHousecalls();
}
DAO
imports
@Dao
public abstract class RevivDao implements RevivDaoInterface {
@Transaction
public void upsert(HouseCall houseCall){
try {
this.insert(houseCall);
} catch (SQLiteConstraintException exception) {
this.update(houseCall);
Log.e(TAG, "upsert: ", exception);
}
}
@Transaction
public void upsert(List<HouseCall> houseCall){
for(HouseCall hc : houseCall) {
try {
this.insert(hc);
} catch (SQLiteConstraintException exception) {
this.update(hc);
Log.e(TAG, "upsert: ", exception);
}
}
}
}
感谢@pskink,我找到了解决将数据更新到我的 ViewModel 的延迟的方法。
为了解决这个问题,我不得不实现 PagedListAdapter。
在 build.gradle(模块)文件中
implementation 'android.arch.paging:runtime:1.0.1'
在 DAO 中
@Query("SELECT * from housecalls where status = :status")
public abstract DataSource.Factory<Integer, HouseCall> getHousecallPagesByStatus(String status);
在视图模型中
//declare a LiveData of PagedList
LiveData<PagedList<HouseCall>> openhousecallPages;
// Define Configuration for Paged List
Config pagedListConfig = (new PagedList.Config.Builder()).setEnablePlaceholders(true)
.setPrefetchDistance(10)
.setPageSize(20).build();
// Function to access the data
public LiveData<PagedList<HouseCall>> getOpenhousecallPages(){
openhousecallPages = new LivePagedListBuilder<>(database.revivDao().getHousecallPagesByStatus("open"),
pagedListConfig).build();
return openhousecallPages;
}
设置 PagedListAdapter
package packagename;
import static android.content.ContentValues.TAG;
public class HouseCallPagedAdapter extends PagedListAdapter<HouseCall, HouseCallViewHolder>{
protected HouseCallPagedAdapter(@NonNull DiffUtil.ItemCallback<HouseCall> diffCallback) {
super(diffCallback);
}
public HouseCallPagedAdapter(@NonNull DiffUtil.ItemCallback diffcallback){
super(diffcallback);
}
@NonNull
@Override
public HouseCallViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int i) {
return new HouseCallViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.card_housecall_request, parent, false),parent.getContext());
}
@Override
public void onBindViewHolder(@NonNull in.portmanteau.reviv.Adapters.HouseCallViewHolder holder, int i) {
holder.bindTo(getItem(i));
}
}
定义一个具有以下结构的ViewHolder
public class HouseCallViewHolder extends RecyclerView.ViewHolder{
// declare values, elements, etc
public HouseCallViewHolder(View itemView, Context mContext) {
super(itemView);
// set up UI elements
}
void bindTo(final HouseCall houseCall){
this.houseCall = houseCall;
//populate values, set onClickListeners, etc.
}
}
最后,在您的Activity/Fragment!
中使用适配器// Implement an DiffUtil.ItemCallback
private DiffUtil.ItemCallback<HouseCall> diffCallback = new DiffUtil.ItemCallback<HouseCall>() {
@Override
public boolean areItemsTheSame(@NonNull HouseCall houseCall, @NonNull HouseCall newhouseCall) {
return houseCall.getHousecallid().equalsIgnoreCase(newhouseCall.getHousecallid()) ;
}
@Override
public boolean areContentsTheSame(@NonNull HouseCall houseCall, @NonNull HouseCall newhouseCall) {
return houseCall.isTheSame(newhouseCall);
}
};
HouseCallPagedAdapter houseCallPagedAdapter = new HouseCallPagedAdapter(diffCallback);
viewModel.getOpenhousecallPages().observe(this, houseCallPagedAdapter::submitList);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity().getApplicationContext(), LinearLayoutManager.VERTICAL, false));
recyclerView.setItemAnimator (new DefaultItemAnimator());
recyclerView.setAdapter(houseCallPagedAdapter);