recyclerview适配器为服务器创建回调接口时,NPE崩溃

时间:2019-07-26 09:57:17

标签: android android-recyclerview

我具有以下自定义onBindViewHolder方法-


 @Override
    protected void onBindViewHolder(@NonNull InstantVideoViewHolder holder, int position, @NonNull Video model) {
        boolean isWinner = checkIsWinner(model);
        holder.populate(model, this.getRef(position).getKey(), isWinner);
        Timber.d("onBindViewHolder: %s", holder.getTag());

        // resume active if needed
        if (position == getActivePos()) {
            markActiveVH(holder);
        }

    }

在holder.populate()方法中的

是我实例化所有Video模型变量的地方,包括回调对象。我的视图持有人类实现了回调接口及其方法,因此在视图持有人内部,我通过应用this设置了侦听器。

这是我的populate()方法-


public void populate(Video video, String videoUid, boolean isWinner) {
        // load models
        mVideo = video;
        mVideoKey = videoUid;
        mContestKey = video.getContestId();
        mPresenter.setArgs(videoUid, true, true);
        setupDetailsView();

        // init viewcounts
        mViewStatsFinder = new DBViewStatsFinder(mContestKey, mVideoKey);
        mViewStatsFinder.setListener(viewCountsListener);
        mViewStatsFinder.findViewsCount();

        // info of owner
        ImageUtil.loadImageUrl(video.getProfile().getPhotoUrl(), imageViewOwner);
        tvOwner.setText(video.getProfile().getName());
//        tvPublishedDate.setText(video.getFormattedPublishDate());
//        tvContestInfo.setText(getFormattedContest(video));

        // video title
        mSongNameTv.setText(mVideo.getSongName());

        // contest number
        mContestNumber.setText( mVideo.getFormattedContestTitle(getContext()).replace("1MD Indian Talent ", ""));

        // winner
        tvOwner.setTextColor(ContextCompat.getColor(getContext(), isWinner ? R.color.gold_win : R.color.black));
//        tvPublishedDate.setTextColor(ContextCompat.getColor(getContext(), isWinner ? R.color.gold_win : R.color.black));
        imWinner.setVisibility(isWinner ? View.VISIBLE : View.GONE);

        // download
        mImageCover.setVisibility(View.VISIBLE);
        ImageUtil.loadFirebaseImageUri(video.getThumbUri(), mImageCover);
        loadMediaSource();

        // subscribe if ready
        initDBEvents();
        subscribe();

        toggleCommentsLoadProgressbar(true);
        toggleVotesLoadProgressbar(true);
        toggleViewCountsProgressbar(true);
    }


我的问题是,对于某些用户,当Internet连接速度慢时,他们在向下滚动列表时会崩溃-单击该视图,然后向服务器发送回调,这可能需要一些时间。在那个时候,用户可能会向下滚动列表,当结果返回时,它变成一个空对象,因为相关的列表项已经被回收,这意味着它现在为空。

我当时正在考虑将整个holder.populate方法移至onCreateViewHolder,但问题是populate()方法需要一些无法在onCreateView()上使用的参数。

任何想法都会很有帮助。

编辑-

这是我的堆栈跟踪-


Fatal Exception: java.lang.NullPointerException
Attempt to invoke interface method 'void com.onemdtalent.app.presenters.sharelanding.ShareLandingPresenter$IVideoLikeSuccess.onLikeSuccess()' on a null object reference
com.onemdtalent.app.presenters.sharelanding.ShareLandingPresenter.completeUpdateVote (ShareLandingPresenter.java:455)
com.onemdtalent.app.presenters.sharelanding.ShareLandingPresenter.lambda$processVoteVideo$4 (ShareLandingPresenter.java:449)
com.onemdtalent.app.presenters.sharelanding.-$$Lambda$ShareLandingPresenter$F7TrCfo8uo_HGe9BlaEqncdfi1w.onComplete (Unknown Source:2)
com.google.android.gms.tasks.zzj.run (Unknown Source:4)
android.os.Handler.handleCallback (Handler.java:873)
android.os.Handler.dispatchMessage (Handler.java:99)
android.os.Looper.loop (Looper.java:193)
android.app.ActivityThread.main (ActivityThread.java:6692)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:858)


这是跟踪的相应代码-



private void processVoteVideo(boolean liked) {
            // check vote to see if user has already voted. if so, check to see if user has liked. if liked - return. else - add like. if user has not voted, the like is processed as a vote. *orit*
            DataSnapshot snapshot = getExistVote();
            if (snapshot != null) {
                Vote vote = snapshot.getValue(Vote.class);
                if (vote != null) {
                    // close if already matched
                    if (vote.isLiked() == liked) {
                        getView().close();
                        //toggle like - if liked to unlike and vice versa

                    } else {

                        snapshot.getRef().setValue(vote, (DatabaseError databaseError, DatabaseReference databaseReference) -> {
                            completeUpdateVote(databaseError == null);
                        });
                    }
                    completeUpdateVote(false);
                    return;
                }
            }
            VotesDBHelper.addVote(getUid(), mProfile, mVideo.getContestId(), mVideo.getContestStageKey(), mVideoKey, liked).addOnCompleteListener(task -> { //todo task was once called multiple times!! check why.
                if (task.isSuccessful()) {
                    Timber.v("Success likeVideo");
                } else {
                    Timber.w(task.getException(), "Error processLikeVideo");
                }
                completeUpdateVote(task.isSuccessful());
            });
    }

    private void completeUpdateVote(boolean successful) {
        if (successful) {
            mIVideoLikeSuccessListener.onLikeSuccess(); // --> here is the crash, the listener is null when receving the callback from the server because the list item has been recycled faster than the callback arriving. 
        } else {
            mIVideoLikeSuccessListener.onLikeFail();
        }
    }


以下代码是我的演示者^。

2 个答案:

答案 0 :(得分:0)

我不会为您编写整个代码,但我将向您展示一种更好的方法(imo):

想法是将api调用移出RecyclerView或其适配器。视图模型或演示者应对此负责。所以:

  1. 首先将一个scroll listener添加到包含recyclerview的视图模型(或“活动”)中:

    recyclerView.addOnScrollListener(...);

  2. 在滚动侦听器中,接下来通过方法onScrollStateChanged检查newState参数是否等于RecyclerView.SCROLL_STATE_IDLE

  3. 如果是,那么您需要通过调用LinearLayoutManager.findFirstCompletelyVisibleItemPosition()

  4. 查找第一个完全可见的项目
  5. 现在您有了可见项目的索引,您可以从列表中的项目中获取所需的信息并加载视频

注意:您需要按照我在第2步中提到的方法检查RecyclerView.SCROLL_STATE_DRAGGING并分别取消对api的调用。

答案 1 :(得分:0)

我找到了所需的解决方案。

我将relevent API回调的实例传递给ViewHolder的构造函数,然后仅在onViewCreated中创建它,所以这就是我想要的。

感谢大家的帮助。