From 64fdaf5753929cdc1622230ae785f1f6a1f6d7f9 Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Fri, 23 Oct 2020 13:05:24 +0100 Subject: [PATCH 1/4] Add AmbientImageLoader --- coil/README.md | 19 ++++++++ coil/api/coil.api | 6 +++ .../chrisbanes/accompanist/coil/CoilTest.kt | 45 +++++++++++++++++-- .../dev/chrisbanes/accompanist/coil/Coil.kt | 40 ++++++++++++----- 4 files changed, 94 insertions(+), 16 deletions(-) diff --git a/coil/README.md b/coil/README.md index 3d0716575..c031b9a35 100644 --- a/coil/README.md +++ b/coil/README.md @@ -93,6 +93,25 @@ CoilImage( Accompanist Coil supports GIFs through Coil's own GIF support. Follow the [setup instructions](https://coil-kt.github.io/coil/gifs/) and it should just work. +## Custom ImageLoader + +If you wish to provide a default [`ImageLoader`](...) to use across all of your `CoilImage` +calls, we provide the `AmbientImageLoader` ambient. You can see it like so: + +``` kotlin +val imageLoader = ImageLoader.Builder(context) + // customize the ImageLoader as needed + .build() + +Providers(AmbientImageLoader provides imageLoader) { + // This will automatically use the value of AmbientImageLoader + CoilImage( + data = ..., + onRequestCompleted = { latch.countDown() } + ) +} +``` + ## Download ```groovy diff --git a/coil/api/coil.api b/coil/api/coil.api index 7549a9e3a..4c449631e 100644 --- a/coil/api/coil.api +++ b/coil/api/coil.api @@ -8,5 +8,11 @@ public final class dev/chrisbanes/accompanist/coil/CoilImage { public static fun ErrorResult$annotations ()V public static fun RequestResult$annotations ()V public static fun SuccessResult$annotations ()V + public static final fun getAmbientImageLoader ()Landroidx/compose/runtime/ProvidableAmbient; +} + +public final class dev/chrisbanes/accompanist/coil/CoilImageConstants { + public static final field INSTANCE Ldev/chrisbanes/accompanist/coil/CoilImageConstants; + public final fun defaultImageLoader (Landroidx/compose/runtime/Composer;I)Lcoil/ImageLoader; } diff --git a/coil/src/androidTest/java/dev/chrisbanes/accompanist/coil/CoilTest.kt b/coil/src/androidTest/java/dev/chrisbanes/accompanist/coil/CoilTest.kt index 73c465cab..218dfeb1b 100644 --- a/coil/src/androidTest/java/dev/chrisbanes/accompanist/coil/CoilTest.kt +++ b/coil/src/androidTest/java/dev/chrisbanes/accompanist/coil/CoilTest.kt @@ -19,6 +19,7 @@ package dev.chrisbanes.accompanist.coil 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.ui.Modifier import androidx.compose.ui.graphics.Color @@ -67,6 +68,7 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger @LargeTest @RunWith(JUnit4::class) @@ -194,11 +196,10 @@ class CoilTest { // Build a custom ImageLoader with a fake EventListener val eventListener = object : EventListener { - var startCalled = 0 - private set + val startCalled = AtomicInteger() override fun fetchStart(request: ImageRequest, fetcher: Fetcher<*>, options: Options) { - startCalled++ + startCalled.incrementAndGet() } } val imageLoader = ImageLoader.Builder(context) @@ -218,7 +219,43 @@ class CoilTest { latch.await(5, TimeUnit.SECONDS) // Verify that our eventListener was invoked - assertThat(eventListener.startCalled).isAtLeast(1) + assertThat(eventListener.startCalled.get()).isAtLeast(1) + } + + @OptIn(ExperimentalCoilApi::class) + @Test + fun basicLoad_customImageLoader_ambient() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val latch = CountDownLatch(1) + + // Build a custom ImageLoader with a fake EventListener + val eventListener = object : EventListener { + val startCalled = AtomicInteger() + + override fun fetchStart(request: ImageRequest, fetcher: Fetcher<*>, options: Options) { + startCalled.incrementAndGet() + } + } + val imageLoader = ImageLoader.Builder(context) + .eventListener(eventListener) + . + .build() + + composeTestRule.setContent { + Providers(AmbientImageLoader provides imageLoader) { + CoilImage( + data = server.url("/image"), + modifier = Modifier.preferredSize(128.dp, 128.dp), + onRequestCompleted = { latch.countDown() } + ) + } + } + + // Wait for the onRequestCompleted to release the latch + latch.await(5, TimeUnit.SECONDS) + + // Verify that our eventListener was invoked + assertThat(eventListener.startCalled.get()).isAtLeast(1) } @OptIn(ExperimentalCoroutinesApi::class) diff --git a/coil/src/main/java/dev/chrisbanes/accompanist/coil/Coil.kt b/coil/src/main/java/dev/chrisbanes/accompanist/coil/Coil.kt index 169b0934e..43f7da161 100644 --- a/coil/src/main/java/dev/chrisbanes/accompanist/coil/Coil.kt +++ b/coil/src/main/java/dev/chrisbanes/accompanist/coil/Coil.kt @@ -22,6 +22,7 @@ package dev.chrisbanes.accompanist.coil import androidx.compose.foundation.Image import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.runtime.staticAmbientOf import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter @@ -43,6 +44,21 @@ import dev.chrisbanes.accompanist.imageloading.ImageLoadState import dev.chrisbanes.accompanist.imageloading.MaterialLoadingImage import dev.chrisbanes.accompanist.imageloading.toPainter +/** + * Ambient containing the preferred [ImageLoader] to use in [CoilImage]. + */ +val AmbientImageLoader = staticAmbientOf { null } + +object CoilImageConstants { + /** + * Returns the default [ImageLoader] value for the `imageLoader` parameter in [CoilImage]. + */ + @Composable + fun defaultImageLoader(): ImageLoader { + return AmbientImageLoader.current ?: ContextAmbient.current.imageLoader + } +} + /** * Creates a composable that will attempt to load the given [data] using [Coil], and provides * complete content of how the current state is displayed: @@ -63,8 +79,8 @@ import dev.chrisbanes.accompanist.imageloading.toPainter * @param data The data to load. See [ImageRequest.Builder.data] for the types allowed. * @param modifier [Modifier] used to adjust the layout algorithm or draw decoration content. * @param requestBuilder Optional builder for the [ImageRequest]. - * @param imageLoader The [ImageLoader] to use when requesting the image. Defaults to [Coil]'s - * default image loader. + * @param imageLoader The [ImageLoader] to use when requesting the image. Defaults to + * [CoilImageConstants.defaultImageLoader]. * @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. @@ -75,7 +91,7 @@ fun CoilImage( data: Any, modifier: Modifier = Modifier, requestBuilder: (ImageRequest.Builder.(size: IntSize) -> ImageRequest.Builder)? = null, - imageLoader: ImageLoader = ContextAmbient.current.imageLoader, + imageLoader: ImageLoader = CoilImageConstants.defaultImageLoader(), shouldRefetchOnSizeChange: (currentResult: ImageLoadState, size: IntSize) -> Boolean = DefaultRefetchOnSizeChangeLambda, onRequestCompleted: (ImageLoadState) -> Unit = EmptyRequestCompleteLambda, content: @Composable (imageLoadState: ImageLoadState) -> Unit @@ -112,8 +128,8 @@ fun CoilImage( * set, one will be set on the request using the layout constraints. * @param modifier [Modifier] used to adjust the layout algorithm or draw decoration content. * @param requestBuilder Optional builder for the [ImageRequest]. - * @param imageLoader The [ImageLoader] to use when requesting the image. Defaults to [Coil]'s - * default image loader. + * @param imageLoader The [ImageLoader] to use when requesting the image. Defaults to + * [CoilImageConstants.defaultImageLoader]. * @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. @@ -124,7 +140,7 @@ fun CoilImage( request: ImageRequest, modifier: Modifier = Modifier, requestBuilder: (ImageRequest.Builder.(size: IntSize) -> ImageRequest.Builder)? = null, - imageLoader: ImageLoader = ContextAmbient.current.imageLoader, + imageLoader: ImageLoader = CoilImageConstants.defaultImageLoader(), shouldRefetchOnSizeChange: (currentResult: ImageLoadState, size: IntSize) -> Boolean = DefaultRefetchOnSizeChangeLambda, onRequestCompleted: (ImageLoadState) -> Unit = EmptyRequestCompleteLambda, content: @Composable (imageLoadState: ImageLoadState) -> Unit @@ -196,8 +212,8 @@ fun CoilImage( * @param fadeIn Whether to run a fade-in animation when images are successfully loaded. * Default: `false`. * @param requestBuilder Optional builder for the [ImageRequest]. - * @param imageLoader The [ImageLoader] to use when requesting the image. Defaults to [Coil]'s - * default image loader. + * @param imageLoader The [ImageLoader] to use when requesting the image. Defaults to + * [CoilImageConstants.defaultImageLoader]. * @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. @@ -211,7 +227,7 @@ fun CoilImage( colorFilter: ColorFilter? = null, fadeIn: Boolean = false, requestBuilder: (ImageRequest.Builder.(size: IntSize) -> ImageRequest.Builder)? = null, - imageLoader: ImageLoader = ContextAmbient.current.imageLoader, + imageLoader: ImageLoader = CoilImageConstants.defaultImageLoader(), shouldRefetchOnSizeChange: (currentResult: ImageLoadState, size: IntSize) -> Boolean = DefaultRefetchOnSizeChangeLambda, onRequestCompleted: (ImageLoadState) -> Unit = EmptyRequestCompleteLambda, error: @Composable ((ImageLoadState.Error) -> Unit)? = null, @@ -269,8 +285,8 @@ fun CoilImage( * @param loading Content to be displayed when the request is in progress. * @param fadeIn Whether to run a fade-in animation when images are successfully loaded. Default: `false`. * @param requestBuilder Optional builder for the [ImageRequest]. - * @param imageLoader The [ImageLoader] to use when requesting the image. Defaults to [Coil]'s - * default image loader. + * @param imageLoader The [ImageLoader] to use when requesting the image. Defaults to + * [CoilImageConstants.defaultImageLoader]. * @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. @@ -284,7 +300,7 @@ fun CoilImage( colorFilter: ColorFilter? = null, fadeIn: Boolean = false, requestBuilder: (ImageRequest.Builder.(size: IntSize) -> ImageRequest.Builder)? = null, - imageLoader: ImageLoader = ContextAmbient.current.imageLoader, + imageLoader: ImageLoader = CoilImageConstants.defaultImageLoader(), shouldRefetchOnSizeChange: (currentResult: ImageLoadState, size: IntSize) -> Boolean = DefaultRefetchOnSizeChangeLambda, onRequestCompleted: (ImageLoadState) -> Unit = EmptyRequestCompleteLambda, error: @Composable ((ImageLoadState.Error) -> Unit)? = null, From 4f80b782e4b2b89d12d45ffa761b36e75681dd63 Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Fri, 23 Oct 2020 13:08:36 +0100 Subject: [PATCH 2/4] Fix link to ImageLoader doc --- coil/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coil/README.md b/coil/README.md index c031b9a35..0cadafd6c 100644 --- a/coil/README.md +++ b/coil/README.md @@ -95,7 +95,7 @@ Accompanist Coil supports GIFs through Coil's own GIF support. Follow the [setup ## Custom ImageLoader -If you wish to provide a default [`ImageLoader`](...) to use across all of your `CoilImage` +If you wish to provide a default [`ImageLoader`](https://coil-kt.github.io/coil/image_loaders/) to use across all of your `CoilImage` calls, we provide the `AmbientImageLoader` ambient. You can see it like so: ``` kotlin From 898d9fdfcb081b3c85863a9e1f674bf965c991cf Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Fri, 23 Oct 2020 13:11:06 +0100 Subject: [PATCH 3/4] Fix test --- .../androidTest/java/dev/chrisbanes/accompanist/coil/CoilTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/coil/src/androidTest/java/dev/chrisbanes/accompanist/coil/CoilTest.kt b/coil/src/androidTest/java/dev/chrisbanes/accompanist/coil/CoilTest.kt index 218dfeb1b..6c0012cd4 100644 --- a/coil/src/androidTest/java/dev/chrisbanes/accompanist/coil/CoilTest.kt +++ b/coil/src/androidTest/java/dev/chrisbanes/accompanist/coil/CoilTest.kt @@ -238,7 +238,6 @@ class CoilTest { } val imageLoader = ImageLoader.Builder(context) .eventListener(eventListener) - . .build() composeTestRule.setContent { From fe5e8513cbd71ac4c0e3e9f2cd6856b60c46d3c6 Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Fri, 23 Oct 2020 13:51:48 +0100 Subject: [PATCH 4/4] Remove unnecessary code form README --- coil/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/coil/README.md b/coil/README.md index 0cadafd6c..0337a7776 100644 --- a/coil/README.md +++ b/coil/README.md @@ -106,8 +106,7 @@ val imageLoader = ImageLoader.Builder(context) Providers(AmbientImageLoader provides imageLoader) { // This will automatically use the value of AmbientImageLoader CoilImage( - data = ..., - onRequestCompleted = { latch.countDown() } + data = ... ) } ```