diff --git a/glide/README.md b/glide/README.md index 2c2acf869..623fecb66 100644 --- a/glide/README.md +++ b/glide/README.md @@ -98,6 +98,28 @@ Accompanist Glide supports GIFs through Glide's own GIF support. There's nothing ![Example GIF](https://media.giphy.com/media/6oMKugqovQnjW/giphy.gif) +## Custom RequestManager + +If you wish to provide a default `RequestManager` to use across all of your `GlideImage` +calls, we provide the `AmbientRequestManager` ambient. + +You can use it like so: + +``` kotlin +val requestManager = Glide.with(...) + // customize the RequestManager as needed + .build() + +Providers(AmbientRequestManager provides requestManager) { + // This will automatically use the value of AmbientRequestManager + GlideImage( + data = ... + ) +} +``` + +For more information on ambients, see [here](https://developer.android.com/reference/kotlin/androidx/compose/runtime/Ambient). + ## Download ```groovy diff --git a/glide/api/glide.api b/glide/api/glide.api index 21ac1d742..afed00c7e 100644 --- a/glide/api/glide.api +++ b/glide/api/glide.api @@ -1,5 +1,11 @@ public final class dev/chrisbanes/accompanist/glide/GlideImage { public static final fun GlideImage (Ljava/lang/Object;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;Landroidx/compose/ui/graphics/ColorFilter;ZLkotlin/jvm/functions/Function2;Lcom/bumptech/glide/RequestManager;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V public static final fun GlideImage (Ljava/lang/Object;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function2;Lcom/bumptech/glide/RequestManager;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V + public static final fun getAmbientRequestManager ()Landroidx/compose/runtime/ProvidableAmbient; +} + +public final class dev/chrisbanes/accompanist/glide/GlideImageConstants { + public static final field INSTANCE Ldev/chrisbanes/accompanist/glide/GlideImageConstants; + public final fun defaultRequestManager (Landroidx/compose/runtime/Composer;I)Lcom/bumptech/glide/RequestManager; } diff --git a/glide/src/androidTest/java/dev/chrisbanes/accompanist/glide/GlideTest.kt b/glide/src/androidTest/java/dev/chrisbanes/accompanist/glide/GlideTest.kt index 27336f8db..805745718 100644 --- a/glide/src/androidTest/java/dev/chrisbanes/accompanist/glide/GlideTest.kt +++ b/glide/src/androidTest/java/dev/chrisbanes/accompanist/glide/GlideTest.kt @@ -20,11 +20,13 @@ import android.graphics.drawable.Drawable import androidx.compose.foundation.Image import androidx.compose.foundation.Text import androidx.compose.foundation.layout.preferredSize +import androidx.compose.runtime.Providers import androidx.compose.runtime.collectAsState import androidx.compose.runtime.onCommit import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.ColorPainter +import androidx.compose.ui.platform.ViewAmbient import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import androidx.test.filters.LargeTest @@ -279,6 +281,57 @@ class GlideTest { .assertIsDisplayed() } + @Test + fun customRequestManager_param() { + val latch = CountDownLatch(1) + val loaded = mutableListOf() + + composeTestRule.setContent { + // Create a RequestManager with a listener which updates our loaded list + val glide = Glide.with(ViewAmbient.current) + .addDefaultRequestListener(SimpleRequestListener { model -> loaded += model }) + + GlideImage( + data = server.url("/image").toString(), + requestManager = glide, + modifier = Modifier.preferredSize(128.dp, 128.dp), + onRequestCompleted = { latch.countDown() } + ) + } + + // Wait for the onRequestCompleted to release the latch + latch.await(5, TimeUnit.SECONDS) + + // Assert that the listener was called + assertThat(loaded).hasSize(1) + } + + @Test + fun customRequestManager_ambient() { + val latch = CountDownLatch(1) + val loaded = mutableListOf() + + composeTestRule.setContent { + // Create a RequestManager with a listener which updates our loaded list + val glide = Glide.with(ViewAmbient.current) + .addDefaultRequestListener(SimpleRequestListener { model -> loaded += model }) + + Providers(AmbientRequestManager provides glide) { + GlideImage( + data = server.url("/image").toString(), + modifier = Modifier.preferredSize(128.dp, 128.dp), + onRequestCompleted = { latch.countDown() } + ) + } + } + + // Wait for the onRequestCompleted to release the latch + latch.await(5, TimeUnit.SECONDS) + + // Assert that the listener was called + assertThat(loaded).hasSize(1) + } + @Test fun errorStillHasSize() { val latch = CountDownLatch(1) diff --git a/glide/src/androidTest/java/dev/chrisbanes/accompanist/glide/SimpleRequestListener.kt b/glide/src/androidTest/java/dev/chrisbanes/accompanist/glide/SimpleRequestListener.kt new file mode 100644 index 000000000..b2b568a74 --- /dev/null +++ b/glide/src/androidTest/java/dev/chrisbanes/accompanist/glide/SimpleRequestListener.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.chrisbanes.accompanist.glide + +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target + +/** + * Simple wrapper around RequestListener for use in tests + */ +internal class SimpleRequestListener( + private val onComplete: (Any) -> Unit +) : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any, + target: Target?, + isFirstResource: Boolean + ): Boolean { + onComplete(model) + return false + } + + override fun onResourceReady( + resource: Any?, + model: Any, + target: Target?, + dataSource: DataSource?, + isFirstResource: Boolean + ): Boolean { + onComplete(model) + return false + } +} diff --git a/glide/src/main/java/dev/chrisbanes/accompanist/glide/GlideImage.kt b/glide/src/main/java/dev/chrisbanes/accompanist/glide/GlideImage.kt index f1f8bd87d..f9a0c68ac 100644 --- a/glide/src/main/java/dev/chrisbanes/accompanist/glide/GlideImage.kt +++ b/glide/src/main/java/dev/chrisbanes/accompanist/glide/GlideImage.kt @@ -22,6 +22,7 @@ package dev.chrisbanes.accompanist.glide import android.graphics.drawable.Drawable import androidx.compose.foundation.Image import androidx.compose.runtime.Composable +import androidx.compose.runtime.staticAmbientOf import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter @@ -46,6 +47,22 @@ import dev.chrisbanes.accompanist.imageloading.toPainter import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.suspendCancellableCoroutine +/** + * Ambient containing the preferred [RequestManager] to use in [GlideImage]. + */ +val AmbientRequestManager = staticAmbientOf { null } + +object GlideImageConstants { + /** + * Returns the default [RequestManager] value for the `requestManager` parameter + * in [GlideImage]. + */ + @Composable + fun defaultRequestManager(): RequestManager { + return AmbientRequestManager.current ?: Glide.with(ViewAmbient.current) + } +} + /** * Creates a composable that will attempt to load the given [data] using [Glide], and provides * complete content of how the current state is displayed: @@ -66,7 +83,8 @@ import kotlinx.coroutines.suspendCancellableCoroutine * @param data The data to load. * @param modifier [Modifier] used to adjust the layout algorithm or draw decoration content. * @param requestBuilder Optional builder for the [RequestBuilder]. - * @param requestManager The [RequestManager] to use when requesting the image. Defaults to `Glide.with(view)` + * @param requestManager The [RequestManager] to use when requesting the image. Defaults to the + * current value of [AmbientRequestManager]. * @param shouldRefetchOnSizeChange Lambda which will be invoked when the size changes, allowing * optional re-fetching of the image. Return true to re-fetch the image. * @param onRequestCompleted Listener which will be called when the loading request has finished. @@ -77,7 +95,7 @@ fun GlideImage( data: Any, modifier: Modifier = Modifier, requestBuilder: (RequestBuilder.(size: IntSize) -> RequestBuilder)? = null, - requestManager: RequestManager = Glide.with(ViewAmbient.current), + requestManager: RequestManager = GlideImageConstants.defaultRequestManager(), shouldRefetchOnSizeChange: (currentResult: ImageLoadState, size: IntSize) -> Boolean = DefaultRefetchOnSizeChangeLambda, onRequestCompleted: (ImageLoadState) -> Unit = EmptyRequestCompleteLambda, content: @Composable (imageLoadState: ImageLoadState) -> Unit @@ -133,7 +151,8 @@ fun GlideImage( * @param fadeIn Whether to run a fade-in animation when images are successfully loaded. * Default: `false`. * @param requestBuilder Optional builder for the [RequestBuilder]. - * @param requestManager The [RequestManager] to use when requesting the image. Defaults to `Glide.with(view)` + * @param requestManager The [RequestManager] to use when requesting the image. Defaults to the + * current value of [AmbientRequestManager]. * @param shouldRefetchOnSizeChange Lambda which will be invoked when the size changes, allowing * optional re-fetching of the image. Return true to re-fetch the image. * @param onRequestCompleted Listener which will be called when the loading request has finished. @@ -147,7 +166,7 @@ fun GlideImage( colorFilter: ColorFilter? = null, fadeIn: Boolean = false, requestBuilder: (RequestBuilder.(size: IntSize) -> RequestBuilder)? = null, - requestManager: RequestManager = Glide.with(ViewAmbient.current), + requestManager: RequestManager = GlideImageConstants.defaultRequestManager(), shouldRefetchOnSizeChange: (currentResult: ImageLoadState, size: IntSize) -> Boolean = DefaultRefetchOnSizeChangeLambda, onRequestCompleted: (ImageLoadState) -> Unit = EmptyRequestCompleteLambda, error: @Composable ((ImageLoadState.Error) -> Unit)? = null,