具有2个不同数据库引用的FirebaseRecyclerAdapter - 对滚动的负面影响

时间:2017-02-27 15:33:46

标签: android firebase firebase-realtime-database recycler-adapter android-viewholder

enter image description here

我想做的简单事情(见图)

显示包含来自Firebase中2个不同位置的信息的视图,以便其以专业方式滚动向上和向下

我有一个电影列表,我希望用户指定一个评级并看到它

在DB中,我创建了2个结构,一面是电影列表,另一面是每个用户的评级

使用FirebaseRecyclerAdapter

时出现问题

我的问题是在列表中快速滚动,来自第二个引用(评级)的信息的可视化是在不同的时间(异步调用)加载的,这不是看到这个(小)延迟构建视图是可以接受的。这是FirebaseRecyclerView的限制吗?

因为viewHolders在recycleView中被重用,所以我每次在populateView()中重置并重新加载评级值,这没有帮助。一旦检索到,我就不知道如果用户滚动视图再次获取它们(请参阅populateView()中的setOnlistener

在populateView中设置一个监听器也会导致populateView()被执行的次数与监听器的数量一样多(如果向上和向下滚动多次)。

解决方案/解决方法?

有没有正确的方法来防止这个问题?还是限制? 我的实现的性能如何,其中监听器位于populateView()内并且创建了多个监听器?

我正在思考的一些事情:

  • 防止viewHolders被回收并加载一次?
  • 覆盖RecyclerView的其他一些方法?我尝试使用parseSnapshot(),但这是同样的问题......
  • 更改数据库结构以将所有信息放在一个列表中(我认为这不是好的,因为这意味着将每个用户的评级信息添加到电影列表中)
  • 在评级部分添加加载微调器,以便仅在对firebase的异步调用完成(不喜欢)时显示评级,而不会产生今天的效果:“在用户面前更改星形颜色”。

我的实施

来自FirebaseRecyclerAdapter

 @Override
protected void populateViewHolder(final MovieViewHolder viewHolder, final     Movie movie, final int position) {

    String movieId = this.getRef(position).getKey();

    // Oblidged to show no rating at the beginning because otherwise
    // if a viewHolder is reused it has the values from another movie
    viewHolder.showNoRating();

    //---------------------------------------------
    // Set values in the viewHolder from the model 
    //---------------------------------------------
    viewHolder.movieTitle.setText(movie.getTitle());
    viewHolder.movieDescription.setText(movie.getDescription());

    //-----------------------------------------------------
    // Ratings info are in another DB location... get them
    // but call is asynchronous so PROBLEM when SCROLLING!
    //-----------------------------------------------------
    DatabaseReference ratingMovieRef = mDbRef.child(Constants.FIREBASE_LOCATION_RATINGS).child(currentUserId).child(movieId);
 ratingQuoteRef.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {


            RatingMovie ratingMovie = dataSnapshot.getValue(RatingMovie.class);
            Rating rating = Rating.NO_RATING;
            if (ratingMovie != null) {
                rating = Rating.valueOf(ratingMovie.getRating());
            }

            // Set the rating in the viewholder (through anhelper method)
            viewHolder.showActiveRating(rating);
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    });

}
来自MovieViewHolder的

public class QuoteViewHolder extends RecyclerView.ViewHolder {

public CardView cardView;
public TextView movieTitle;
public TextView movieDescription;
public ImageView ratingOneStar;
public ImageView ratingTwoStar;
public ImageView ratingThreeStar;

public QuoteViewHolder(View itemView) {

    super(itemView);
    movieTitle = (TextView)itemView.findViewById(R.id.movie_title);
    movieDescription = (TextView)itemView.findViewById(R.id.movie_descr);

    // rating
    ratingOneStar = (ImageView)itemView.findViewById(R.id.rating_one);
    ratingTwoStar = (ImageView)itemView.findViewById(R.id.rating_two);
    ratingThreeStar = (ImageView)itemView.findViewById(R.id.rating_three);
}

/**
* Helper to show the color on stars depending on rating value 
*/
public void showActiveRating(Rating rating){

    if (rating.equals(Rating.ONE)) {
        // just set the good color on ratingOneStar and the others
        ...
    }
    else if (rating.equals(Rating.TWO)) {
        // just set the good color
        ...
    } else if (rating.equals(Rating.THREE)) {
       // just set the good color
       ...
   }


/**
 * Initialize the rating icons to unselected.
 * Important because the view holder can be reused and if not initalised values from other moviecan be seen
 */
public void initialiseNoRating(){
 ratingOneStar.setColorFilter(ContextCompat.getColor(itemView.getContext(), R.color.light_grey)); 
    ratingTwoStar.setColorFilter(....
    ratingThreeStar.SetColorFilter(...
}

1 个答案:

答案 0 :(得分:2)

您可以使用ChildEventListener对评级进行缓存。基本上只是为Ratings节点创建一个单独的,并将它存储在Map中。然后使用RecyclerAdapter,如果评级可用,您将从地图中检索,如果不是,则评级监听器在下载评级后立即更新recyclerview。这是您可以采取的一种策略,在执行此操作时,您必须手动从FirebaseUI库中复制/粘贴一些类,并将一些字段设置为公共,以使其正常工作。

用法就是这样的

private MovieRatingConnection ratingConnection;

   // inside onCreate

    ratingConnection = new MovieRatingConnection(userId, new MovieRatingConnection.RatingChangeListener() {
        @Override
        public void onRatingChanged(DataSnapshot dataSnapshot) {
            if (recyclerAdapter != null) {
                if (dataSnapshot != null) {
                    int index = recyclerAdapter.snapshots.getIndexForKey(dataSnapshot.getKey());
                    recyclerAdapter.notifyItemChanged(index);
                }
            }
        }
    });

    Query movieQuery = FirebaseDatabase.getInstance().getReference().child("Movies");
    recyclerAdapter = new FirebaseRecyclerAdapter(movieQuery...) {
        @Override
        public void populateViewHolder(RecyclerView.ViewHolder viewHolder, Object model, int position) {
            //...
            final String key = getRef(position).getKey();
            viewHolder.showActiveRating(ratingConnection.getRating(key));
        }
    };

和MovieRatingConnection将是这样的类

public class MovieRatingConnection {

    private MovieRatingListener listener;

    public MovieRatingConnection(String userId, RatingChangeListener changeListener) {
        Query query = FirebaseDatabase.getInstance().getReference().child("MovieRatings").child(userId);
        listener = new MovieRatingListener(query, changeListener);
    }

    public Rating getRating(String key) {
        return listener.getRating(key);
    }

    public void cleanup() {
        if (listener != null) {
            listener.unregister();
        }
    }



    public static class MovieRatingListener implements ChildEventListener {

        public interface RatingChangeListener {
            public void onRatingChanged(DataSnapshot snapshot);

        }

        private Query query;
        private HashMap<String, Rating> ratingMap = new HashMap<>();
        private RatingChangeListener changeListener;


        public MovieRatingListener(Query query, RatingChangeListener changeListener) {
            this.query = query;
            this.changeListener = changeListener;
            query.addChildEventListener(this);

        }

        @Override
        public void onChildAdded(DataSnapshot dataSnapshot, String s) {
            if (dataSnapshot != null) {
                ratingMap.put(dataSnapshot.getKey(), dataSnapshot.getValue(Rating.class));
                changeListener.onRatingChanged(dataSnapshot);
            }
        }

        @Override
        public void onChildChanged(DataSnapshot dataSnapshot, String s) {
            if (dataSnapshot != null) {
                ratingMap.put(dataSnapshot.getKey(), dataSnapshot.getValue(Rating.class));
                changeListener.onRatingChanged(dataSnapshot);
            }
        }

        @Override
        public void onChildRemoved(DataSnapshot dataSnapshot) {
            ratingMap.remove(dataSnapshot.getKey());
            changeListener.onRatingChanged(null);
        }

        @Override
        public void onChildMoved(DataSnapshot dataSnapshot, String s) {

        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }

        public Rating getRating(String key) {
            if (ratingMap.get(key) != null) {
                return ratingMap.get(key);
            } else {
                return new Rating(); // default value/null object
            }
        }

        public void unregister() {
            query.removeEventListener(this);
        }
    }
}