Skip to content

Instantly share code, notes, and snippets.

@keima
Last active March 31, 2024 21:58
Show Gist options
  • Save keima/1b8cda30aec8cd50fec7743d2ccfa777 to your computer and use it in GitHub Desktop.
Save keima/1b8cda30aec8cd50fec7743d2ccfa777 to your computer and use it in GitHub Desktop.
LifecycleOwner implemented RecyclerView ViewHolder & Adapter (concept design)
import android.os.Bundle
import android.util.Log
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import app.keima.android.recyclerviewsandbox.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.recyclerView.apply {
adapter = MyAdapter(
arrayOf(
"A", "B", "C", "D",
"A", "B", "C", "D",
"A", "B", "C", "D",
"A", "B", "C", "D"
)
)
}
}
}
class MyAdapter(private val dataset: Array<String>) :
LifecycleRecyclerAdapter<MyAdapter.MyViewHolder>() {
class MyViewHolder(private val textView: TextView) : LifecycleViewHolder(textView) {
private val observer = MyObserver()
init {
lifecycle.addObserver(observer)
}
fun bindData(data: String) {
textView.text = data
observer.data = data
}
}
class MyObserver() : LifecycleObserver {
var data: String = "?"
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreate() {
Log.d("MyObserver", "appear: $data")
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
Log.d("MyObserver", "disappear: $data")
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(TextView(parent.context).apply {
setPadding(8, 40, 8, 40)
})
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bindData("${dataset[position]} $position")
}
override fun getItemCount() = dataset.size
}
import androidx.recyclerview.widget.RecyclerView
abstract class LifecycleRecyclerAdapter<T : LifecycleViewHolder> : RecyclerView.Adapter<T>() {
override fun onViewAttachedToWindow(holder: T) {
super.onViewAttachedToWindow(holder)
holder.onAppear()
}
override fun onViewDetachedFromWindow(holder: T) {
super.onViewDetachedFromWindow(holder)
holder.onDisappear()
}
}
import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.recyclerview.widget.RecyclerView
abstract class LifecycleViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView), LifecycleOwner {
private val lifecycleRegistry = LifecycleRegistry(this)
init {
lifecycleRegistry.currentState = Lifecycle.State.INITIALIZED
}
fun onAppear() {
lifecycleRegistry.currentState = Lifecycle.State.CREATED
}
fun onDisappear() {
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
}
override fun getLifecycle(): Lifecycle {
return lifecycleRegistry
}
}
@jonathansds
Copy link

@jonathands, this is my repo you can see there https://github.com/abhinavjiit/baseRecyclerview/blob/master/app/src/main/java/com/example/pristencare/adapter/RecyclerViewImageAdapter.kt

Amazing! I will have a look at it now, try to implement and run Leakcanary! Thanks a lot!!

if you got something wrong or any memory leak, plz let me know as well

Sure! I am implementing it now. Might take a lil bit because I want to fully understand the logic behind it but I should be able to fully test it today :)

@jonathands
Copy link

@jonathansds
Copy link

@abhinavjiit It's working like a charm! No memory leaks, no excess of objects allocated... It's just perfectly working! Well done!

@abhinavjiit
Copy link

abhinavjiit commented Aug 18, 2021

@abhinavjiit It's working like a charm! No memory leaks, no excess of objects allocated... It's just perfectly working! Well done!

@jonathansds Cool bro.

@YouJiacheng
Copy link

@abhinavjiit
Hi! - I think there is possibly something wrong: RecyclerView.getChildCount and RecyclerView.getChildAt only return visible items.

@YouJiacheng
Copy link

YouJiacheng commented Jun 17, 2022

My implementation

class SimpleLifeCycleOwnerImpl : SimpleLifeCycleOwner {
    private var lifecycleRegistry: LifecycleRegistry? = null

    override fun getLifecycle() =
        lifecycleRegistry ?: run {
            initialize()
            lifecycleRegistry!!
        }

    override fun initialize() {
        // The object can be revived, create a new lifecycle
        lifecycleRegistry?.run {
            if (currentState != Lifecycle.State.DESTROYED)
                throw IllegalStateException("can be revived only after destroyed, get $currentState")
        }
        lifecycleRegistry = LifecycleRegistry(this)
    }

    override fun handleLifecycleEvent(event: Lifecycle.Event) {
        lifecycleRegistry!!.handleLifecycleEvent(event)
    }
}
// kotlin cannot inherit from type parameter like C++, we must use this class as base
// otherwise we need to copy the code for each adapter
abstract class LifecycleAdapter<VH : RecyclerView.ViewHolder>(lifecycleOwner: LifecycleOwner) :
    RecyclerView.Adapter<VH>() {
    private var recyclerView: RecyclerView? = null

    init {
        val observer = object : LifecycleEventObserver {
            private fun visibleChildApply(block: SimpleLifeCycleOwner.() -> Unit) =
                recyclerView?.run {
                    for (i in 0 until childCount)
                        getChildAt(i)?.let {
                            (getChildViewHolder(it) as? SimpleLifeCycleOwner)?.block()
                        }
                } ?: Unit

            private fun allChildApply(block: SimpleLifeCycleOwner.() -> Unit) =
                recyclerView?.run {
                    for (i in 0 until itemCount)
                        (findViewHolderForAdapterPosition(i) as? SimpleLifeCycleOwner)?.block()
                } ?: Unit

            override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) =
                when (event) {
                    Lifecycle.Event.ON_DESTROY -> allChildApply { handleLifecycleEvent(event) }
                    else -> visibleChildApply { handleLifecycleEvent(event) }
                }
        }
        lifecycleOwner.lifecycle.addObserver(observer)
    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        if (this.recyclerView != null)
            throw IllegalStateException("can be attached to only one RecyclerView")
        this.recyclerView = recyclerView
    }

    override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
        throw IllegalStateException("must not be detached")
    }

    override fun onViewAttachedToWindow(holder: VH) =
        (holder as? SimpleLifeCycleOwner)?.run {
            handleLifecycleEvent(Lifecycle.Event.ON_START)
            handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
        } ?: Unit

    override fun onViewDetachedFromWindow(holder: VH) =
        (holder as? SimpleLifeCycleOwner)?.run {
            handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
            handleLifecycleEvent(Lifecycle.Event.ON_STOP)
        } ?: Unit

    override fun onBindViewHolder(holder: VH, position: Int) =
        (holder as? SimpleLifeCycleOwner)?.run {
            initialize()
            handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
            // logical views are created here
        } :? Unit

    override fun onViewRecycled(holder: VH) =
        (holder as? SimpleLifeCycleOwner)?.run {
            handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
        } ?: Unit
}

@TangKe
Copy link

TangKe commented Jul 20, 2022

It will cause memory leak, when RecyclerView detach from window, the onViewDetachedFromWindow callback will not invoked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment