1

I am having trouble optimizing my RecyclerView. When I cold start the app, the first scroll is always janky.

I followed this, but it does not help as I am not sure what causes the lag.

Here is my RecyclerView's content (list_item.xml):

<android.support.v7.widget.CardView 
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="86dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginRight="8dp">

<android.support.constraint.ConstraintLayout
    android:id="@+id/list_item_constraint_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?android:attr/selectableItemBackground">

    <TextView
        android:id="@+id/list_item_akcija_ime"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:ellipsize="marquee"
        android:fontFamily="sans-serif"
        android:singleLine="true"
        android:text="@string/ime_akcije"
        android:textColor="@color/common_google_signin_btn_text_dark_focused"
        android:textSize="18sp"
        app:layout_constraintEnd_toStartOf="@+id/list_item_eye"
        app:layout_constraintStart_toEndOf="@+id/list_item_slika"
        app:layout_constraintTop_toTopOf="@+id/list_item_slika" />

    <TextView
        android:id="@+id/list_item_akcija_datum"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:fontFamily="sans-serif"
        android:text="@string/_1_7_2019"
        android:textSize="12sp"
        app:layout_constraintStart_toStartOf="@+id/list_item_akcija_ime"
        app:layout_constraintTop_toBottomOf="@+id/list_item_akcija_ime" />

    <ImageView
        android:id="@+id/list_item_slika"
        android:layout_width="110dp"
        android:layout_height="70dp"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:adjustViewBounds="true"
        android:contentDescription="@string/slika_akcije"
        android:scaleType="centerCrop"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <LinearLayout
        android:id="@+id/list_item_starosti_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="@+id/list_item_slika"
        app:layout_constraintStart_toStartOf="@+id/list_item_akcija_ime" />

    <ImageView
        android:id="@+id/list_item_fire"
        android:layout_width="21dp"
        android:layout_height="22dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:contentDescription="@string/fire"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/list_item_fires"
        app:layout_constraintTop_toBottomOf="@+id/list_item_eye"
        app:srcCompat="@drawable/campfire" />

    <ImageView
        android:id="@+id/list_item_eye"
        android:layout_width="21dp"
        android:layout_height="22dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:contentDescription="@string/eye"
        app:layout_constraintBottom_toTopOf="@+id/list_item_fire"
        app:layout_constraintEnd_toStartOf="@+id/list_item_clicks"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="packed"
        app:srcCompat="@drawable/eye" />

    <TextView
        android:id="@+id/list_item_clicks"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:textSize="12sp"
        app:layout_constraintBottom_toBottomOf="@+id/list_item_eye"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/list_item_eye"
        tools:text="159" />

    <TextView
        android:id="@+id/list_item_fires"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="60"
        android:textColor="@color/colorAccent"
        android:textSize="12sp"
        app:layout_constraintBottom_toBottomOf="@+id/list_item_fire"
        app:layout_constraintStart_toStartOf="@+id/list_item_clicks"
        app:layout_constraintTop_toBottomOf="@+id/list_item_eye" />

</android.support.constraint.ConstraintLayout>

This is what I do in onBindViewHolder

@Override
public void onBindViewHolder(@NonNull AkcijaViewHolder akcija, int i) {
    akcija.bindData(akcije.get(i), mOnItemClickListener, context); 
}

My bindData method is pretty expensive, but if I am right this shouldn't affect the performance?

public void bindData(final Akcija akcija, final AkcijaAdapter.OnItemClickListener onItemClickListener, Context context) {

    /* Set Akcija's ime to the TextView */
    imeTextView.setText(akcija.getIme());
    imeTextView.setSelected(true);

    /* Set date and show it */
    Date date = new Date(akcija.getDatum() * 1000);
    DateFormat dateFormat = new SimpleDateFormat("d. M. yyyy", Locale.getDefault()); // Format for 13. 4. 2019
    String strDate = dateFormat.format(date);
    datumTextView.setText(strDate);

    /* Set clicks */
    klikTextView.setText(String.valueOf(akcija.getClicks()));

    /* Draw starosti circle_mc images */
    String starosti = akcija.getStarost(); // get starosti...
    String[] starostiArr = starosti.split(" "); // and split them into strings

    linearLayout.removeAllViews();
    for (String starost : starostiArr) { // loop through all starosti. For each starost draw it by calling drawStarostSlika and pass in the drawable of circle
        switch (starost) {
            case "M":
                drawStarostSlika(R.drawable.circle_mu);
                break;
            case "MČ":
                drawStarostSlika(R.drawable.circle_mc);
                break;
            case "GG":
                drawStarostSlika(R.drawable.circle_gg);
                break;
            case "PP":
                drawStarostSlika(R.drawable.circle_pp);
                break;
            case "RR":
                drawStarostSlika(R.drawable.circle_rr);
                break;
            case "G":
                drawStarostSlika(R.drawable.circle_gr);
                break;
            default:
                break;
        }
    }

    /* Load image into slikaImageView from Firebase Storage */
    GlideApp.with(context)
            .load(storageReference.child(akcija.getIme() + ".jpg"))
            .thumbnail(0.5f)
            .transition(DrawableTransitionOptions.withCrossFade())
            .diskCacheStrategy(DiskCacheStrategy.DATA)
            .into(slikaImageView);


    /* Make each item clickable. On click, run the method passed in to adapter from anywhere. */
    listItemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            onItemClickListener.onItemClick(akcija, v);
        }
    });
}

This is how I populate the RecyclerView

private DatabaseReference mDatabaseReference = FirebaseDatabase.getInstance().getReference().child("akcije"); 
private StorageReference storageReference = FirebaseStorage.getInstance().getReference();

List<Akcija> akcije = new ArrayList<>();
AkcijaAdapter adapter;
RecyclerView recyclerView;

Context context; 

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

    context = MainActivity.this;



    mDatabaseReference.addValueEventListener(valueEventListener); // set listener for the method defined below

    adapter = new AkcijaAdapter(context, akcije, onItemClickListener); // for now, add empty akcija's so the list is not skipped
    recyclerView = findViewById(R.id.list);
    recyclerView.setHasFixedSize(true); 
    recyclerView.setAdapter(adapter);
    recyclerView.setLayoutManager(new LinearLayoutManager(context)); 
}

/* When Firebase's databases changes update the recycler view */
ValueEventListener valueEventListener = new ValueEventListener() {

    @Override
    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
        for (DataSnapshot ds : dataSnapshot.getChildren()) {
            akcije.add(new Akcija(
                    ds.getKey(), // the key is the same as Akcija's name
                    ds.child("datum").getValue(Long.class), // milliseconds since 1970
                    ds.child("starost").getValue().toString(),
                    ds.child("link").getValue().toString(),
                    ds.child("klik").getValue(Integer.class)
            ));
        }

        /* Sort akcija's by date */
        Collections.sort(akcije, new Comparator<Akcija>() {
            @Override
            public int compare(Akcija o1, Akcija o2) {
                return o2.getDatum().compareTo(o1.getDatum());
            }
        });

        adapter = new AkcijaAdapter(context, akcije, onItemClickListener); // update the list and
        recyclerView.setAdapter(adapter); // set the adapter
        mDatabaseReference.removeEventListener(this); // only update the app when it's ran
    }

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

    }
};


AkcijaAdapter.OnItemClickListener onItemClickListener = new AkcijaAdapter.OnItemClickListener() {
    @Override
    public void onItemClick(Akcija akcija, View listItemView) {
    // do stuff here
    }
};

}

I also thought using ConstraintLayout was optimal, but I don't know if my list_item is still too expensive?

Bor
  • 13
  • 3
  • use CPU method profiling in Profilers tab – EpicPandaForce Apr 17 '19 at 18:15
  • Why do you have 2 empty linear layouts in that RecyclerView item? – 92AlanC Apr 17 '19 at 18:30
  • @EpicPandaForce Thank you, I tried that but am not able to determine whether something is normal or not. onCreateViewHolder takes up 326,000 microseconds, and onBindViewHolder is 160,000 microseconds. – Bor Apr 17 '19 at 18:33
  • @AlanC92 In bindData I am populating the views with small xml images (little dots), though removing that line doesn't increase the performance – Bor Apr 17 '19 at 18:35
  • Probably it's the way you're fetching data to and populating the RecyclerView that's bringing the lag. Could you show us how you're fetching the data? – 92AlanC Apr 17 '19 at 18:39
  • @AlanC92 updated. – Bor Apr 17 '19 at 18:55

1 Answers1

0

The most probable reason why you're getting those lags is that every time you're receiving new data from your database you're setting a new adapter to your RecyclerView.

Try using a ListAdapter instead of a RecyclerView.Adapter as it's also more functional with reacting to changes. With that adapter you'll set it to your RecyclerView only once, then every time you receive new data you should call something like adapter.submitList(myNewData);

92AlanC
  • 1,327
  • 2
  • 14
  • 33
  • Thanks for help, but after the first snapshot I remove the event listener. I only receive data once. (mDatabaseReference.removeEventListener(this);) – Bor Apr 17 '19 at 20:05