Skip to content

Commit

Permalink
Add AmbientImageLoader for Coil (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbanes committed Oct 26, 2020
1 parent d116e30 commit 85fa82a
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 16 deletions.
18 changes: 18 additions & 0 deletions coil/README.md
Expand Up @@ -93,6 +93,24 @@ 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`](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
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 = ...
)
}
```

## Download

```groovy
Expand Down
6 changes: 6 additions & 0 deletions coil/api/coil.api
Expand Up @@ -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;
}

Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -218,7 +219,42 @@ 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)
Expand Down
40 changes: 28 additions & 12 deletions coil/src/main/java/dev/chrisbanes/accompanist/coil/Coil.kt
Expand Up @@ -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
Expand All @@ -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<ImageLoader?> { 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:
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand All @@ -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,
Expand Down

0 comments on commit 85fa82a

Please sign in to comment.