Skip to content

Instantly share code, notes, and snippets.

@keima
Last active March 31, 2024 21:58
Show Gist options
  • Star 22 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • 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
}
}
@fmatosqg
Copy link

I'm implementing something very close to this but I'm having a nasty bug. Sometimes my row gets attached to the new data before detaching from the old one, causing wrong data to display or bad data to flicker before settling. Have you experienced this?

@kibotu
Copy link

kibotu commented Jun 24, 2019

onAppear should also call
lifecycleRegistry.markState(Lifecycle.State.STARTED)

also markState is deprecated now, and currentState should be used :)

thanks for sharing though

@sudeshim3
Copy link

sudeshim3 commented Sep 12, 2019

onAppear should also call
lifecycleRegistry.markState(Lifecycle.State.STARTED)

also markState is deprecated now, and currentState should be used :)

thanks for sharing though

yes, markState is deprecated. write onAppear() and onDisappear() as

fun onAppear() {
    lifecycleRegistry.currentState = Lifecycle.State.CREATED
  }

  fun onDisappear() {
    lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
  }

@kibotu
Copy link

kibotu commented Sep 12, 2019

@keima
Copy link
Author

keima commented Oct 23, 2019

2019/10/24 Updated.

  • Migrate AndroidX
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.recyclerview:recyclerview:1.0.0'

    def lifecycle_version = "2.1.0"
    implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
    kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"

@DorisGM
Copy link

DorisGM commented Jan 17, 2020

Hi I try this。 But if you destroyed the lifecycleRegister , the reuse viewholder's lifcyclRegistery will never send event to liveData anymore. It will lose some status. What can i do fix it? Help thanks

@abhinavjiit
Copy link

abhinavjiit commented Jul 15, 2021

Hi I try this。 But if you destroyed the lifecycleRegister , the reuse viewholder's lifcyclRegistery will never send event to liveData anymore. It will lose some status. What can i do fix it? Help thanks

init {
lifecycleRegistry.currentState = Lifecycle.State.INITIALIZED
lifecycleRegistry.currentState = Lifecycle.State.CREATED
}

fun onAppear() {
    lifecycleRegistry.currentState = Lifecycle.State.STARTED
    lifecycleRegistry.currentState = Lifecycle.State.RESUMED
}

fun onDisappear() {
    lifecycleRegistry.currentState = Lifecycle.State.STARTED
    lifecycleRegistry.currentState = Lifecycle.State.CREATED
}

fun Destroy()
{
lifecycleRegistry.currentState = Lifecycle.State. DESTROYED
}

ans call this destroy method when you are going out of screen means this method should be observed from parent lifecycle(recyclerview lifecycle), so when you are changing the screen or exiting from app.

@jonathansds
Copy link

Hi I try this。 But if you destroyed the lifecycleRegister , the reuse viewholder's lifcyclRegistery will never send event to liveData anymore. It will lose some status. What can i do fix it? Help thanks

init {
lifecycleRegistry.currentState = Lifecycle.State.INITIALIZED
lifecycleRegistry.currentState = Lifecycle.State.CREATED
}

fun onAppear() {
    lifecycleRegistry.currentState = Lifecycle.State.STARTED
    lifecycleRegistry.currentState = Lifecycle.State.RESUMED
}

fun onDisappear() {
    lifecycleRegistry.currentState = Lifecycle.State.STARTED
    lifecycleRegistry.currentState = Lifecycle.State.CREATED
}

fun Destroy()
{
lifecycleRegistry.currentState = Lifecycle.State. DESTROYED
}

ans call this destroy method when you are going out of screen means this method should be observed from parent lifecycle(recyclerview lifecycle), so when you are changing the screen or exiting from app.

Hi @abhinavjiit, I am trying to implement this. Would you mind giving us a light about how to properly call that destroy() method? I am out of ideas on how to do it...

@abhinavjiit
Copy link

@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!!

@abhinavjiit
Copy link

abhinavjiit commented Aug 18, 2021

@jonathansds, 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

@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

@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

could you fix your mention so I don't get the notifications?

@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