Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ package com.skydoves.landscapist.glide
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalView
Expand Down Expand Up @@ -187,4 +190,48 @@ internal class GlideImageTest {
assertThat(state[0], instanceOf(GlideImageState.Failure::class.java))
}
}

@Test
fun staleFailure_fromPreviousRequest_doesNotOverrideLatestSuccess() {
val terminalStatesForLatestModel = ArrayList<GlideImageState>()
val latestModel = android.R.drawable.ic_dialog_info
var imageModel by mutableStateOf<Any?>("https://landscapist.invalid/should-fail.jpg")

composeTestRule.setContent {
GlideImage(
imageModel = { imageModel },
modifier = Modifier
.size(128.dp, 128.dp)
.testTag(TAG_GLIDE),
imageOptions = ImageOptions(contentScale = ContentScale.Crop),
onImageStateChanged = { state ->
if (
imageModel == latestModel &&
(state is GlideImageState.Success || state is GlideImageState.Failure)
) {
terminalStatesForLatestModel.add(state)
}
},
)
}

composeTestRule.runOnIdle {
imageModel = latestModel
}

composeTestRule.waitUntil(10_000) {
terminalStatesForLatestModel.isNotEmpty()
}

composeTestRule.runOnIdle {
assertThat(
terminalStatesForLatestModel.first(),
instanceOf(GlideImageState.Success::class.java),
)
val hasFailureForLatestModel = terminalStatesForLatestModel.any {
it is GlideImageState.Failure
}
assertThat(hasFailureForLatestModel, `is`(false))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,14 @@ internal class FlowCustomTarget constructor(

private val sizeReadyCallbacks = mutableListOf<SizeReadyCallback>()

private var failException: Throwable? = null

/** onResourceReady will be handled by [FlowRequestListener]. */
override fun onResourceReady(resource: Any, transition: Transition<in Any>?) = Unit

override fun onLoadStarted(placeholder: Drawable?) {
producerScope?.trySendBlocking(ImageLoadState.Loading)
}

override fun onLoadFailed(errorDrawable: Drawable?) {
producerScope?.trySendBlocking(
ImageLoadState.Failure(
data = errorDrawable,
reason = failException,
),
)
override fun onLoadFailed(ignoredErrorDrawable: Drawable?) {
producerScope?.channel?.close()
}

Expand Down Expand Up @@ -120,10 +112,6 @@ internal class FlowCustomTarget constructor(
this.producerScope = producerScope
}

fun updateFailException(throwable: Throwable?) {
failException = throwable
}

private val Constraints.inferredIntSize: IntSize
get() {
if (imageOptions.isValidSize) return imageOptions.requestSize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import kotlinx.coroutines.channels.trySendBlocking
*/
internal class FlowRequestListener(
private val producerScope: ProducerScope<ImageLoadState>,
private val failException: (Throwable?) -> Unit,
) : RequestListener<Any> {

override fun onLoadFailed(
Expand All @@ -37,9 +36,15 @@ internal class FlowRequestListener(
target: Target<Any>,
isFirstResource: Boolean,
): Boolean {
failException.invoke(e)
// return false so that the load failed will handle this.
return false
producerScope.trySendBlocking(
ImageLoadState.Failure(
data = null,
reason = e,
),
)
producerScope.channel.close()
// return true so target callbacks don't emit into a different active request scope.
return true
}

override fun onResourceReady(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,7 @@ private fun GlideImage(
callbackFlow {
target.setProducerScope(this)

val flowRequestListener = FlowRequestListener(this) {
target.updateFailException(it)
}
val flowRequestListener = FlowRequestListener(this)

// start the image request into the target.
val typedBuilder = requestManager.buildRequestBuilder(
Expand Down