joe
joe

Reputation: 1371

onBindViewHolder not called after RecyclerView Adapter notifyDataSetChanged()

I'm getting some data from Firebase database and I'm trying to populate RecyclerView adapter with it. After Adapter's notifyDataSetChanged() is called, screen blinks and nothing happens, I couldn't even catch a breakpoint inside onBindViewHolder.

Here is my code:

POJO class:

public class Result2 implements Serializable {

private int score;
private String userName;

public Result2(int score, String userName) {
    this.score = score;
    this.userName = userName;
}

public int getScore() {
    return score;
}


public String getUserName() {
    return userName;
}

}

This is my activitys layout called activity_results.xml that contains RecyclerView

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:orientation="vertical">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:orientation="horizontal"
    android:weightSum="2">

    <TextView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:text="USERNAME"
        android:textColor="#000"
        android:textSize="16sp"/>

    <TextView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:text="SCORE"
        android:textColor="#000"
        android:textSize="16sp"/>

</LinearLayout>

<View
    android:layout_width="match_parent"
    android:layout_height="1dp"
    android:background="#000"
    />

<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
                                        xmlns:app="http://schemas.android.com/apk/res-auto"
                                        android:id="@+id/recycler_view"
                                        android:layout_width="match_parent"
                                        android:layout_height="match_parent"
                                        android:clipToPadding="false"
                                        android:paddingTop="10dp"
                                        android:scrollbars="vertical"
                                        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

Here is my Adapters ViewHolder layout called score_view_holder.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:orientation="vertical">

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="50dp"
              android:orientation="horizontal"
              android:weightSum="2">

    <TextView
        android:id="@+id/username"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:textSize="14sp"/>

    <TextView
        android:id="@+id/score"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:textSize="14sp"/>


</LinearLayout>

<View
    android:layout_width="match_parent"
    android:layout_height="1dp"
    android:layout_marginEnd="10dp"
    android:layout_marginStart="10dp"
    android:background="#4545"/>

So it will contain two horizontal TextViews and a View as a line below..

Here is my Adapter:

public class ScoreAdapter extends RecyclerView.Adapter<ScoreAdapter.ScoreViewHolder> {

private List<Result2> results = new ArrayList<>();

public void setResults(List<Result2> results) {
    this.results.clear();
    this.results.addAll(results);
    notifyDataSetChanged();
}

@Override
public ScoreAdapter.ScoreViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.score_view_holder, parent, false);
    return new ScoreAdapter.ScoreViewHolder(view);
}

@Override
public void onBindViewHolder(ScoreAdapter.ScoreViewHolder holder, int position) {
    Result2 result = getResult(position);
    if (result != null) {
        holder.setUsername(result.getUserName() != null ? result.getUserName() : "-");
        holder.setScore(String.valueOf(result.getScore()));
    }
}

private Result2 getResult(int position) {
    return !results.isEmpty() ? results.get(position) : null;
}

@Override
public int getItemCount() {
    return results.size();
}

public class ScoreViewHolder extends RecyclerView.ViewHolder {

    private TextView username;
    private TextView score;

    public ScoreViewHolder(View itemView) {
        super(itemView);
        username = itemView.findViewById(R.id.username);
        score = itemView.findViewById(R.id.score);
    }

    public void setUsername(String username) {
        this.username.setText(username);
    }

    public void setScore(String score) {
        this.score.setText(score);
    }

}
}

It should get List of Result2 objects and just set text in those two TextViews (username and score)

And finally my Activity where I'm trying to notify adapter from:

public class Results extends AppCompatActivity {

private DatabaseReference mDatabase;
private ScoreAdapter scoreAdapter;
private RecyclerView recyclerView;
private List<Result2> results = new ArrayList<>();


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_results);

    recyclerView = findViewById(R.id.recycler_view);
    scoreAdapter = new ScoreAdapter();
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    recyclerView.setAdapter(scoreAdapter);

    mDatabase = FirebaseDatabase.getInstance().getReference();

    loadResults();
}

private void loadResults() {

    mDatabase.child("Users").addValueEventListener(new ValueEventListener() {
                                                       @Override
                                                       public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

                                                           for (DataSnapshot childSnapshot : dataSnapshot.getChildren()) {
                                                               Result2 result = childSnapshot.getValue(Result2.class);

                                                               if (result != null) {
                                                                   results.add(result);
                                                               }
                                                           }
                                                           Toast.makeText(Results.this, String.valueOf(results.size()), Toast.LENGTH_SHORT).show();
                                                           scoreAdapter.setResults(results);

                                                       }

                                                       @Override
                                                       public void onCancelled(@NonNull DatabaseError databaseError) {

                                                       }
                                                   }

    );
}
}

So after for loop is done, Toast shows correct number of results, adapter setResults method is called, there i receive correct number of results, here is a picture:

List of results inside setResults()

But after notifyDataSetChanged() is called, screen just blinks and everything is blank... methods inside onBindViewHolder can not be reached after putting breakpoints on them.. Any idea what's wrong here?

Upvotes: 8

Views: 6636

Answers (3)

Marcell Harmaci
Marcell Harmaci

Reputation: 103

I know you already set recyclerView.setLayoutManager(new LinearLayoutManager(this)), but in case it would help someone else, I had the same behavior in my app and the issue was the missing layoutManager. It needs to be set before the RecyclerView can arrange the items.

Either in the layout XML, or in Kotlin/Java

<RecyclerView
    android:layoutManager="LinearLayoutManager"
    ... />
recyclerView.layoutManager = LinearLayoutManager(this)

Upvotes: 0

Bolt Nealon
Bolt Nealon

Reputation: 1

I have the same issue where I call notifyDataSetChanged but the onBindViewHolder isn't called. Only when I touch and try to scroll, onBindViewHolder is called.

I solved mine by add a mini scroll after notifying the adapter:

binding.rvwContent.scrollBy(0,1) // this is scroll down

Upvotes: 0

Alex Mamo
Alex Mamo

Reputation: 138969

There are several problems in your code. The first one is that you are calling the setResults() method and pass your results list inside your ScoreAdapter class and not to the constrcutor. In order to solve this, change your setResults() method to become a constructor like this:

public ScoreAdapter(List<Result2> results) {
    this.results.clear();
    this.results.addAll(results);
    notifyDataSetChanged();
}

The adapter must also be instantiated and set to your RecyclerView inside the callback. So to solve this, simply move the following lines of code:

ScoreAdapter scoreAdapter = new ScoreAdapter(result);
recyclerView.setAdapter(scoreAdapter);

Right after the following line:

Toast.makeText(Results.this, String.valueOf(results.size()), Toast.LENGTH_SHORT).show();

Don't also forget to comment this line of code: scoreAdapter.setResults(results);.

See now, to instantiate the adapter you need to pass the result list to the constructor.

Also please also don't forget to add the public no-argument constructor to your Result2 class. Please see here more details.

Upvotes: 0

Related Questions