Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Conductor / Glide lifecycle integration
import com.bumptech.glide.manager.Lifecycle
import com.bumptech.glide.manager.LifecycleListener
import com.bumptech.glide.util.Util
import java.util.*
/**
* A [com.bumptech.glide.manager.Lifecycle] implementation for tracking and notifying
* listeners of [com.bluelinelabs.conductor.Controller] lifecycle events.
*/
class ControllerLifecycle : Lifecycle {
private val lifecycleListeners = Collections.newSetFromMap(WeakHashMap<LifecycleListener, Boolean>())
private var isStarted: Boolean = false
private var isDestroyed: Boolean = false
/**
* Adds the given listener to the list of listeners to be notified on each lifecycle event.
*
* The latest lifecycle event will be called on the given listener synchronously in this
* method. If the activity or fragment is stopped, [LifecycleListener.onStop]} will be
* called, and same for onStart and onDestroy.
*
* Note - [com.bumptech.glide.manager.LifecycleListener]s that are added more than once
* will have their lifecycle methods called more than once. It is the caller's responsibility to
* avoid adding listeners multiple times.
*/
override fun addListener(listener: LifecycleListener) {
lifecycleListeners.add(listener)
when {
isDestroyed -> listener.onDestroy()
isStarted -> listener.onStart()
else -> listener.onStop()
}
}
override fun removeListener(listener: LifecycleListener) {
lifecycleListeners.remove(listener)
}
fun onStart() {
isStarted = true
for (lifecycleListener in Util.getSnapshot(lifecycleListeners)) {
lifecycleListener.onStart()
}
}
fun onStop() {
isStarted = false
for (lifecycleListener in Util.getSnapshot(lifecycleListeners)) {
lifecycleListener.onStop()
}
}
fun onDestroy() {
isDestroyed = true
for (lifecycleListener in Util.getSnapshot(lifecycleListeners)) {
lifecycleListener.onDestroy()
}
}
}
import com.bluelinelabs.conductor.Controller
import com.bumptech.glide.RequestManager
object ControllerRequestManager {
fun with(glideController: Controller): RequestManager {
if (glideController !is HasGlideSupport) {
throw ClassCastException("glideController must implement HasGlideSupport")
}
if (glideController.activity == null) {
throw IllegalArgumentException("You cannot start a load until the Controller has been bound to a Context.")
}
return (glideController as HasGlideSupport).glideSupport.requestManager
?: throw UninitializedPropertyAccessException("requestManager not yet initialized for the given controller")
}
}
import android.view.ViewGroup
import android.view.LayoutInflater
import com.bluelinelabs.conductor.Controller
class ExampleController : Controller(), HasGlideSupport {
@Suppress("LeakingThis")
override val glideSupport = GlideSupport(this)
@NonNull
override fun onCreateView(@NonNull inflater: LayoutInflater, @NonNull container: ViewGroup): View {
val view = inflater.inflate(R.layout.controller_example, container, false)
val imageView1 = view.findViewById(R.id.imageView1)
ControllerRequestManager.with(this).load("http://goo.gl/gEgYUd").into(imageView1)
return view
}
}
import android.content.Context
import android.view.View
import com.bluelinelabs.conductor.Controller
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
import com.bumptech.glide.manager.RequestManagerTreeNode
class GlideSupport(val controller: Controller) : RequestManagerTreeNode {
private var lifecycle: ControllerLifecycle? = null
var requestManager: RequestManager? = null
private set
init {
controller.addLifecycleListener(object : Controller.LifecycleListener() {
var hasDestroyedGlide: Boolean = false
var hasExited: Boolean = false
override fun postCreateView(controller: Controller, view: View) {
lifecycle = ControllerLifecycle()
requestManager = RequestManager(Glide.get(controller.activity), lifecycle, this@GlideSupport)
hasDestroyedGlide = false
}
override fun postAttach(controller: Controller, view: View) {
lifecycle?.onStart()
}
override fun postDetach(controller: Controller, view: View) {
lifecycle?.onStop()
}
override fun postDestroy(controller: Controller) {
// Last controllers in the backstack may be destroyed without transition (onChangeEnd() not getting called)
val isLast = !controller.router.hasRootController()
if ((hasExited || isLast) && !hasDestroyedGlide) {
destroyGlide()
}
}
override fun onChangeEnd(controller: Controller, changeHandler: ControllerChangeHandler, changeType: ControllerChangeType) {
// onChangeEnd() could be called after postDestroy(). We prefer to release Glide as
// late as possible because releasing Glide clears out all ImageViews and they
// appear blank during a transition.
hasExited = !changeType.isEnter
val viewDestroyed = controller.view == null
if (hasExited && viewDestroyed && !hasDestroyedGlide) {
destroyGlide()
}
}
private fun destroyGlide() {
lifecycle?.onDestroy()
lifecycle = null
requestManager = null
hasDestroyedGlide = true
}
})
}
override fun getDescendants(): Set<RequestManager> = collectRequestManagers(controller)
/**
* Recursively gathers the [RequestManager]s of a given [Controller] and all its child controllers.
* The [Controller]s in the hierarchy must implement [HasGlideSupport] in order for their
* request managers to be collected.
*/
private fun collectRequestManagers(controller: Controller,
collected: MutableSet<RequestManager> = HashSet()
): Set<RequestManager> {
if (!controller.isDestroyed && !controller.isBeingDestroyed) {
if (controller is HasGlideSupport) {
controller.glideSupport.requestManager?.let {
collected.add(it)
}
}
controller.childRouters
.flatMap { childRouter -> childRouter.backstack }
.map { routerTransaction -> routerTransaction.controller() }
.forEach { controlr -> collectRequestManagers(controlr, collected) }
}
return collected
}
}
/**
* Must be implemented by [com.bluelinelabs.conductor.Controller]s to have controller-scoped Glide
* resource management (image request pausing, cancelation, cleanup, etc.)
*/
interface HasGlideSupport {
val glideSupport: GlideSupport
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment