Last active
August 30, 2019 13:56
-
-
Save sevar83/7b5e151233481b629e3d1fb1be7327dc to your computer and use it in GitHub Desktop.
Conductor / Glide lifecycle integration
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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