在 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);