I'm currently using DataBinding with a RecyclerView, and I'm getting pretty severe lag when a list first loads. However, after I scroll past a page and it stops creating new viewholders, everything is fine.
During creation, the only thing I'm doing is inflating a layout using DataBindingUtils
, so I'm wondering if there are parts that can be improved.
Attached below are relevant code snippets. I'm currently using a library, FastAdapter
, so the signatures will not match exactly
When creating a viewholder, I do the following:
override fun createView(ctx: Context, parent: ViewGroup?): View {
val start = System.nanoTime()
val binding: ViewDataBinding = DataBindingUtil.inflate(
LayoutInflater.from(ctx),
layoutRes, parent,
false,
null
)
L.d { "Create view ${(System.nanoTime() - start) / 1000000}" }
return binding.root
}
It appears that for my more complex layout, viewholders take around 30-60 ms each, resulting in notable lag.
Binding and unbinding are like so:
final override fun bindView(holder: ViewHolder, payloads: MutableList<Any>) {
super.bindView(holder, payloads)
val binding = DataBindingUtil.getBinding<Binding>(holder.itemView) ?: return
binding.bindView(holder, payloads)
binding.executePendingBindings()
}
open fun Binding.bindView(holder: ViewHolder, payloads: MutableList<Any>) {
setVariable(BR.model, data)
}
final override fun unbindView(holder: ViewHolder) {
super.unbindView(holder)
val binding = DataBindingUtil.getBinding<Binding>(holder.itemView) ?: return
binding.unbindView(holder)
binding.unbind()
}
open fun Binding.unbindView(holder: ViewHolder) {}
final override fun getViewHolder(v: View): ViewHolder = ViewHolder(v, layoutRes)
Basically, I usually have a single data model that I set, and I implement unbinding myself per viewholder. There doesn't seem to be a problem there.
Reading other articles, it appears that most developers have the same viewholder creation method as I do. Is DataUtilBinding not meant to be done upon creation, or is there something I'm missing?
As an addition, here is my relevant layout xml:
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="model"
type="github.fragment.ShortRepoRowItem" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:windowBackground"
android:foreground="?selectableItemBackground"
android:paddingStart="@dimen/kau_activity_horizontal_margin"
android:paddingTop="@dimen/kau_padding_small"
android:paddingEnd="@dimen/kau_activity_horizontal_margin"
android:paddingBottom="@dimen/kau_padding_small"
tools:context=".activity.MainActivity">
<TextView
android:id="@+id/repo_name"
style="@style/TextAppearance.AppCompat.Medium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@{model.name}"
android:textColor="?android:textColorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/full_names" />
<TextView
android:id="@+id/repo_desc"
style="@style/TextAppearance.AppCompat.Caption"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:text="@{model.description}"
android:textColor="?android:textColorSecondary"
app:goneFlag="@{model.description}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/repo_name"
tools:text="@tools:sample/lorem/random" />
<com.google.android.material.chip.Chip
android:id="@+id/repo_stars"
style="@style/RepoChips"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:chipIcon="@drawable/ic_star_border"
app:compactNumberText="@{model.stargazers.totalCount}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/repo_desc"
app:layout_constraintWidth_percent="0.12"
tools:text="123" />
<com.google.android.material.chip.Chip
android:id="@+id/repo_forks"
style="@style/RepoChips"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:chipIcon="@drawable/ic_fork"
app:compactNumberText="@{model.forkCount}"
app:layout_constraintStart_toEndOf="@id/repo_stars"
app:layout_constraintTop_toBottomOf="@id/repo_desc"
app:layout_constraintWidth_percent="0.12"
tools:text="123" />
<com.google.android.material.chip.Chip
android:id="@+id/repo_issues"
style="@style/RepoChips"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:chipIcon="@drawable/ic_issues"
app:compactNumberText="@{model.issues.totalCount}"
app:layout_constraintStart_toEndOf="@id/repo_forks"
app:layout_constraintTop_toBottomOf="@id/repo_desc"
app:layout_constraintWidth_percent="0.12"
tools:text="1.5k" />
<com.google.android.material.chip.Chip
android:id="@+id/repo_prs"
style="@style/RepoChips"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:chipIcon="@drawable/ic_pull_requests"
app:compactNumberText="@{model.pullRequests.totalCount}"
app:layout_constraintStart_toEndOf="@id/repo_issues"
app:layout_constraintTop_toBottomOf="@id/repo_desc"
app:layout_constraintWidth_percent="0.12"
tools:text="123" />
<com.google.android.material.chip.Chip
android:id="@+id/repo_language"
style="@style/RepoChips"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@{model.primaryLanguage.name}"
app:chipIcon="@drawable/ic_language"
app:languageColor="@{model.primaryLanguage.color}"
app:layout_constraintEnd_toStartOf="@id/repo_date"
app:layout_constraintStart_toEndOf="@id/repo_prs"
app:layout_constraintTop_toBottomOf="@id/repo_desc"
app:layout_constraintWidth_percent="0.25"
tools:text="JavaScript" />
<com.google.android.material.chip.Chip
android:id="@+id/repo_date"
style="@style/RepoChips"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:chipIcon="@drawable/ic_time"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/repo_language"
app:layout_constraintTop_toBottomOf="@id/repo_desc"
app:relativeDateText="@{model.pushedAt}"
app:textStartPadding="4dp"
tools:text="@tools:sample/date/mmddyy" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
I've tried doing the same thing with two linear layouts instead of ConstraintLayout, but it doesn't seem to make much of a difference.
Code snapshot: https://github.com/AllanWang/GitDroid/tree/f802c991580d70470b422186fc43f46b9cfe2465
The Data Binding Library is an Android Jetpack library that allows you to bind UI components in your XML layouts to data sources in your app using a declarative format rather than programmatically, reducing boilerplate code.
The data is provided by a ViewModel. Model-View-ViewModel is a presentation layer pattern that works very well with Data Binding. Here's a diagram: If you're not yet familiar with the ViewModel class from the Architecture Components libraries, you can check out the official documentation.
There's no magic in Data Binding. Everything is resolved at compilation time and it's accessible for you to read in the generated code. Let's work on the progress bar. We want it to: We're going to create custom Binding Adapters for this.
I did some more measurements and found out that the culprit was actually the layout inflation (ie 60ms) instead of just the binding (ie 3ms).
To verify, inflate the view as is without DataBindingUtil
, then call bind
on the view.
From there, the problem was also the use of MaterialChips. After switching 6 material chips to textviews, inflation time went down 3 folds to around 10-30ms.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With