I am currently working with two RecyclerViews, one acting as the parent and the other as the child. The parent RecyclerView consists of child items representing different videos, while the child RecyclerView contains frames within each video. My objective is to adjust the frame rate per second based on user actions such as zooming in or zooming out.
However, when I attempt to submit the updated list to the child RecyclerView after zooming in, the application becomes unresponsive and triggers an Application Not Responding (ANR) error.
Parent RV
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/parentRv"
android:layout_width="0dp"
android:layout_height="50dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/parent_recycler_view_item" />
Child Rv
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:id="@+id/childRv"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:orientation="horizontal"
tools:listitem="@layout/single_frame"/>
Child Item
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="50dp"
android:layout_height="50dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:scaleType="centerCrop"
android:orientation="vertical">
<View
android:id="@+id/dividerLeft"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:background="?android:attr/listDivider"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.25" />
<View
android:id="@+id/dividerRight"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:background="?android:attr/listDivider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.25" />
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="1dp"
android:gravity="center"
android:text="1"
android:textSize="8sp"
app:layout_constraintEnd_toStartOf="@+id/dividerRight"
app:layout_constraintStart_toEndOf="@+id/dividerLeft"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="SmallSp" />
<ImageView
android:id="@+id/img"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
android:src="@drawable/track"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
Parent Adapter
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.funsol.vidzedit.Utils.debug
import com.funsol.vidzedit.databinding.ParentRecyclerViewItemBinding
class ParentAdapter(
private val noOfVideos: Int,
private val currentPosition: (Int) -> Unit
) :
RecyclerView.Adapter<ParentAdapter.MyViewHolder>() {
companion object {
val timeLineAdapter by lazy { TimeLineAdapter() }
}
class MyViewHolder(private val binding: ParentRecyclerViewItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun setAdapterToRv(){
binding.root.apply {
adapter = timeLineAdapter
setRecycledViewPool(RecyclerView.RecycledViewPool())
(layoutManager as LinearLayoutManager).initialPrefetchItemCount = 12
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
ParentRecyclerViewItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun getItemCount(): Int = noOfVideos
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.setAdapterToRv()
currentPosition(position)
}
}
Child Adapter
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.request.CachePolicy
import com.funsol.vidzedit.Utils.debug
import com.funsol.vidzedit.databinding.SingleFrameBinding
import java.io.File
import kotlin.math.ceil
class TimeLineAdapter : RecyclerView.Adapter<TimeLineAdapter.MyViewHolder>() {
companion object{
var listOfFile = mutableListOf<File?>()
}
class MyViewHolder(private val binding: SingleFrameBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(file: File?) {
binding.img.load(file){
memoryCachePolicy(CachePolicy.ENABLED)
diskCachePolicy(CachePolicy.ENABLED)
}
binding.textView.text = if (adapterPosition % 2 == 0) "${adapterPosition}f" else "○"
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
SingleFrameBinding.inflate(LayoutInflater.from(parent.context),parent,false)
)
}
fun updateListOfFile(list: List<File?>) {
val diffCallback = DiffCallback(listOfFile, list)
val diffResult = DiffUtil.calculateDiff(diffCallback)
listOfFile.clear()
listOfFile.addAll(list)
diffResult.dispatchUpdatesTo(this)
}
override fun getItemCount(): Int = listOfFile.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(listOfFile[position])
}
class DiffCallback(private val old:List<File?>,private val new:List<File?>) : DiffUtil.Callback(){
override fun getOldListSize(): Int {
return old.size
}
override fun getNewListSize(): Int {
return new.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return old[oldItemPosition]?.absolutePath == new[newItemPosition]?.absolutePath
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return old[oldItemPosition] == new[newItemPosition]
}
}
}
Main Activity
Here i an updating fps value based in condition but when zoom in/out trigger the APP Freeze
fun onZoomInOut(zoom:Int){
if (zoom < 3)
mainViewModel.increaseFpsValue(10)
else mainViewModel.decreaseFpsValue()
}
Updating Adapter
launch {
mainViewModel.currentFpsValue.combine(mainViewModel.currentPosition) { fps, position ->
Pair(fps, position)
}.collect { pair ->
if (files.isNotEmpty())
withContext(Dispatchers.IO) {
createFrameList(lister = files[pair.second - 1], fps = pair.first) {
dealAdapter(it)
}
}
}
}
CreateFrameList
private fun createFrameList(
lister: List<File?>,
fps: Int,
files: (MutableList<File?>) -> Unit
) {
CoroutineScope(Dispatchers.IO).launch {
val frameList = mutableListOf<File?>()
val steps = findSteps(fps)
var index = 1.0
while (index < lister.size - 1) {
val frame = lister[ceil(index).toInt()]
frameList.add(frame)
index += steps
}
files(frameList)
}
}
private fun findSteps(fps: Int): Double = when (fps) {
1 -> 30.0
10 -> 3.0
20 -> 1.5
else -> 1.0
}
Deal Adapter
private fun dealAdapter(it: MutableList<File?>) {
CoroutineScope(Dispatchers.Main).launch {
ParentAdapter.timeLineAdapter.updateListOfFile(it)
}
}
I was expecting that child rv will be simply updating on zoom in and out. But it cuasing ANR as the app doing too much work on Main thread.