diff --git a/CHANGELOG.md b/CHANGELOG.md index 67b4ef0ced..fa0369ff1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,55 @@ # Changelog +## [2.0.0-rc01] - March 2, 2022 + +Significant changes since `1.4.0`: + +- The minimum supported API is now 21. +- Rework the Jetpack Compose integration. + - `rememberImagePainter` has been renamed to `rememberAsyncImagePainter`. + - Add support for `AsyncImage` and `SubcomposeAsyncImage`. Check out [the documentation](https://coil-kt.github.io/coil/compose/) for more info. + - Deprecate `LocalImageLoader`. Check out the deprecation message for more info. +- Coil 2.0 has its own disk cache implementation and no longer relies on OkHttp for disk caching. + - Use `ImageLoader.Builder.diskCache` and `DiskCache.Builder` to configure the disk cache. + - You **should not** use OkHttp's `Cache` with Coil 2.0 as the cache can be corrupted if a thread is interrupted while writing to it. + - `Cache-Control` and other cache headers are still supported - except `Vary` headers, as the cache only checks that the URLs match. Additionally, only responses with a response code in the range [200..300) are cached. + - Existing disk caches will be cleared when upgrading to 2.0. +- `ImageRequest`'s default `Scale` is now `Scale.FIT` + - This was changed to make `ImageRequest.scale` consistent with other classes that have a default `Scale`. + - Requests with an `ImageViewTarget` still have their `Scale` auto-detected. +- Rework the image pipeline classes: + - `Mapper`, `Fetcher`, and `Decoder` have been refactored to be more flexible. + - `Fetcher.key` has been replaced with a new `Keyer` interface. `Keyer` creates the cache key from the input data. + - Adds `ImageSource`, which allows `Decoder`s to read `File`s directly using Okio's file system API. +- Disable generating runtime not-null assertions. + - If you use Java, passing null as a not-null annotated parameter to a function will no longer throw a `NullPointerException` immediately. Kotlin's compile-time null safety guards against this happening. + - This change allows the library's size to be smaller. +- `BitmapPool` and `PoolableViewTarget` have been removed from the library. +- `VideoFrameFileFetcher` and `VideoFrameUriFetcher` are removed from the library. Use `VideoFrameDecoder` instead, which supports all data sources. +- [`BlurTransformation`](https://github.com/coil-kt/coil/blob/845f39383f332428077c666e3567b954675ce248/coil-base/src/main/java/coil/transform/BlurTransformation.kt) and [`GrayscaleTransformation`](https://github.com/coil-kt/coil/blob/845f39383f332428077c666e3567b954675ce248/coil-base/src/main/java/coil/transform/GrayscaleTransformation.kt) are removed from the library. If you use them, you can copy their code into your project. +- Change `Transition.transition` to be a non-suspending function as it's no longer needed to suspend the transition until it completes. +- Add support for `bitmapFactoryMaxParallelism`, which restricts the maximum number of in-progress `BitmapFactory` operations. This value is 4 by default, which improves UI performance. +- Add support for `interceptorDispatcher`, `fetcherDispatcher`, `decoderDispatcher`, and `transformationDispatcher`. +- Add `GenericViewTarget`, which handles common `ViewTarget` logic. +- Add `ByteBuffer` to the default supported data types. +- `Disposable` has been refactored and exposes the underlying `ImageRequest`'s job. +- Rework the `MemoryCache` API. +- `ImageRequest.error` is now set on the `Target` if `ImageRequest.fallback` is null. +- `Transformation.key` is replaced with `Transformation.cacheKey`. +- Update Kotlin to 1.6.10. +- Update Compose to 1.1.1. +- Update OkHttp to 4.9.3. +- Update Okio to 3.0.0. + +Changes since `2.0.0-alpha09`: + +- Remove the `-Xjvm-default=all` compiler flag. +- Fix failing to load image if multiple requests with must-revalidate/e-tag are executed concurrently. +- Fix `DecodeUtils.isSvg` returning false if there is a new line character after the ` true }, - fadeIn = true, - previewPlaceholder = R.drawable.placeholder - ), - contentDescription = null, - modifier = Modifier.size(128.dp) +val painter = rememberAsyncImagePainter( + model = ImageRequest.Builder(LocalContext.current) + .data("https://example.com/image.jpg") + .size(Size.ORIGINAL) // Set the target size to load the image at. + .build() ) -// coil-compose +if (painter.state is AsyncImagePainter.State.Success) { + // This will be executed during the first composition if the image is in the memory cache. +} + Image( - painter = rememberImagePainter( - data = "https://www.example.com/image.jpg", - onExecute = ExecuteCallback { _, _ -> true }, - builder = { - crossfade(true) - placeholder(R.drawable.placeholder) - transformations(CircleCropTransformation()) - } - ), - contentDescription = null, - modifier = Modifier.size(128.dp) + painter = painter, + contentDescription = stringResource(R.string.description), ) ``` + +## Transitions + +You can enable the built in crossfade transition using `ImageRequest.Builder.crossfade`: + +```kotlin +AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data("https://example.com/image.jpg") + .crossfade(true) + .build(), + contentDescription = null +) +``` + +Custom [`Transition`](transitions.md)s do not work with `AsyncImage`, `SubcomposeAsyncImage`, or `rememberAsyncImagePainter` as they require a `View` reference. `CrossfadeTransition` works due to special internal support. + +That said, it's possible to create custom transitions in Compose by observing the `AsyncImagePainter`'s state: + +```kotlin +SubcomposeAsyncImage( + model = "https://example.com/image.jpg", + contentDescription = null +) { + val state = painter.state + if (state is AsyncImagePainter.State.Success && state.dataSource != DataSource.MEMORY_CACHE) { + // Perform the transition animation. + } + + // Render the content. + SubcomposeAsyncImageContent() +} +``` diff --git a/coil-compose-singleton/src/main/java/coil/compose/SingletonAsyncImagePainter.kt b/coil-compose-singleton/src/main/java/coil/compose/SingletonAsyncImagePainter.kt index fb9eadcb65..b732f3bd44 100644 --- a/coil-compose-singleton/src/main/java/coil/compose/SingletonAsyncImagePainter.kt +++ b/coil-compose-singleton/src/main/java/coil/compose/SingletonAsyncImagePainter.kt @@ -26,7 +26,8 @@ import coil.request.ImageRequest * constraint. For example, to use [AsyncImagePainter] with [LazyRow] or [LazyColumn], you must * set a bounded width or height respectively. * - [AsyncImagePainter.state] will not transition to [State.Success] synchronously during the - * composition phase. Use [SubcomposeAsyncImage] if you need this. + * composition phase. Use [SubcomposeAsyncImage] or set a custom [ImageRequest.Builder.size] value + * if you need this. * * @param model Either an [ImageRequest] or the [ImageRequest.data] value. * @param placeholder A [Painter] that is displayed while the image is loading. @@ -76,7 +77,8 @@ fun rememberAsyncImagePainter( * constraint. For example, to use [AsyncImagePainter] with [LazyRow] or [LazyColumn], you must * set a bounded width or height respectively. * - [AsyncImagePainter.state] will not transition to [State.Success] synchronously during the - * composition phase. Use [SubcomposeAsyncImage] if you need this. + * composition phase. Use [SubcomposeAsyncImage] or set a custom [ImageRequest.Builder.size] value + * if you need this. * * @param model Either an [ImageRequest] or the [ImageRequest.data] value. * @param transform A callback to transform a new [State] before it's applied to the diff --git a/coil-gif/README.md b/coil-gif/README.md index 60409fc8aa..e71d1ee7f2 100644 --- a/coil-gif/README.md +++ b/coil-gif/README.md @@ -5,7 +5,7 @@ Unlike Glide, GIFs are not supported by default. However, Coil has an extension To add GIF support, import the extension library: ```kotlin -implementation("io.coil-kt:coil-gif:1.4.0") +implementation("io.coil-kt:coil-gif:2.0.0-rc01") ``` And add the decoders to your component registry when constructing your `ImageLoader`: @@ -27,4 +27,4 @@ And that's it! The `ImageLoader` will automatically detect any GIFs using their To transform the pixel data of each frame of a GIF, see [AnimatedTransformation](../api/coil-gif/coil-gif/coil.transform/-animated-transformation/index.html). !!! Note - Coil includes two separate decoders to support decoding GIFs. `GifDecoder` supports all API levels, but is slower. `ImageDecoderDecoder` is powered by Android's new [ImageDecoder](https://developer.android.com/reference/android/graphics/ImageDecoder) API which is only available on API 28 and above. `ImageDecoderDecoder` is faster than `GifDecoder` and supports decoding animated WebP images and animated HEIF image sequences. + Coil includes two separate decoders to support decoding GIFs. `GifDecoder` supports all API levels, but is slower. `ImageDecoderDecoder` is powered by Android's [ImageDecoder](https://developer.android.com/reference/android/graphics/ImageDecoder) API which is only available on API 28 and above. `ImageDecoderDecoder` is faster than `GifDecoder` and supports decoding animated WebP images and animated HEIF image sequences. diff --git a/coil-svg/README.md b/coil-svg/README.md index fd50464a2b..a72c1fa0f3 100644 --- a/coil-svg/README.md +++ b/coil-svg/README.md @@ -3,7 +3,7 @@ To add SVG support, import the extension library: ```kotlin -implementation("io.coil-kt:coil-svg:1.4.0") +implementation("io.coil-kt:coil-svg:2.0.0-rc01") ``` And add the decoder to your component registry when constructing your `ImageLoader`: diff --git a/coil-video/README.md b/coil-video/README.md index 680b9b1b82..4f2190ba41 100644 --- a/coil-video/README.md +++ b/coil-video/README.md @@ -3,7 +3,7 @@ To add video frame support, import the extension library: ```kotlin -implementation("io.coil-kt:coil-video:1.4.0") +implementation("io.coil-kt:coil-video:2.0.0-rc01") ``` And add the decoder to your component registry when constructing your `ImageLoader`: diff --git a/docs/faq.md b/docs/faq.md index d8052c5720..f4d6043836 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,6 +1,6 @@ # FAQ -Have a question that isn't part of the FAQ? Check [StackOverflow](https://stackoverflow.com/questions/tagged/coil) with the tag #coil or search our [Github issues](https://github.com/coil-kt/coil/issues). +Have a question that isn't part of the FAQ? Check [StackOverflow](https://stackoverflow.com/questions/tagged/coil) with the tag #coil or search [Github discussions](https://github.com/coil-kt/coil/discussions). ## Can Coil be used with Java projects or mixed Kotlin/Java projects? @@ -10,10 +10,6 @@ Yes! [Read here](java_compatibility.md). [Read here](getting_started.md#preloading). -## How do I set up disk caching? - -[Read here](image_loaders.md#caching). - ## How do I enable logging? Set `logger(DebugLogger())` when [constructing your `ImageLoader`](../getting_started/#singleton). @@ -21,6 +17,38 @@ Set `logger(DebugLogger())` when [constructing your `ImageLoader`](../getting_st !!! Note `DebugLogger` should only be used in debug builds. +## How do I target Java 8? + +Coil requires [Java 8 bytecode](https://developer.android.com/studio/write/java8-support). This is enabled by default on the Android Gradle Plugin 4.2.0 and later and the Kotlin Gradle Plugin 1.5.0 and later. If you're using older versions of the above plugins add the following to your Gradle build script: + +Gradle (`.gradle`): + +```groovy +android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} +``` + +Gradle Kotlin DSL (`.gradle.kts`): + +```kotlin +android { + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} +``` + ## How do I get development snapshots? Add the snapshots repository to your list of repositories: diff --git a/docs/getting_started.md b/docs/getting_started.md index 90aa39bb5b..14dea95671 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -13,52 +13,28 @@ Coil has 8 artifacts published to `mavenCentral()`: * `io.coil-kt:coil-video`: Includes a [decoder](../api/coil-base/coil.decode/-decoder) to support decoding frames from [any of Android's supported video formats](https://developer.android.com/guide/topics/media/media-formats#video-codecs). See [videos](videos.md) for more details. * `io.coil-kt:coil-bom`: Includes a [bill of materials](https://docs.gradle.org/7.2/userguide/platforms.html#sub:bom_import). Importing `coil-bom` allows you to depend on other Coil artifacts without specifying a version. -## Java 8 +## Image Loaders -Coil requires [Java 8 bytecode](https://developer.android.com/studio/write/java8-support). To enable this add the following to your Gradle build script: +[`ImageLoader`](image_loaders.md)s are service classes that execute [`ImageRequest`](image_requests.md)s. `ImageLoader`s handle caching, data fetching, image decoding, request management, bitmap pooling, memory management, and more. -Gradle (`.gradle`): +The default Coil artifact (`io.coil-kt:coil`) includes the singleton `ImageLoader`, which can be accessed using an extension function: `context.imageLoader`. -```groovy -android { - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" - } -} -``` - -Gradle Kotlin DSL (`.gradle.kts`): +The singleton `ImageLoader` can be configured by implementing `ImageLoaderFactory` on your `Application` class: ```kotlin -android { - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" +class MyApplication : Application(), ImageLoaderFactory { + + override fun newImageLoader(): ImageLoader { + return ImageLoader.Builder(this) + .crossfade(true) + .build() } } ``` -## Image Loaders - -[`ImageLoader`](image_loaders.md)s are service classes that execute [`ImageRequest`](image_requests.md)s. `ImageLoader`s handle caching, data fetching, image decoding, request management, bitmap pooling, memory management, and more. New instances can be created and configured using a builder: - -```kotlin -val imageLoader = ImageLoader.Builder(context) - .availableMemoryPercentage(0.25) - .crossfade(true) - .build() -``` - -Coil performs best when you create a single `ImageLoader` and share it throughout your app. This is because each `ImageLoader` has its own memory cache, bitmap pool, and network observer. +Implementing `ImageLoaderFactory` is optional. If you don't, Coil will lazily create an `ImageLoader` with the default values. -It's recommended, though not required, to call [`shutdown`](../api/coil-base/coil/-image-loader/shutdown.html) when you've finished using an image loader. Calling `shutdown` preemptively frees its memory and cleans up any observers. If you only create and use a single `ImageLoader`, you do not need to shut it down as it will be freed when your app is killed. +Check out [the full documentation](image_loaders.md) for more info. ## Image Requests @@ -88,58 +64,9 @@ val request = ImageRequest.Builder(context) val result = imageLoader.execute(request) ``` -## Singleton - -If you are using the `io.coil-kt:coil` artifact, you can set the singleton [`ImageLoader`](image_loaders.md) instance by either: - -- Implementing `ImageLoaderFactory` on your `Application` class (prefer this method): - -```kotlin -class MyApplication : Application(), ImageLoaderFactory { - - override fun newImageLoader(): ImageLoader { - return ImageLoader.Builder(applicationContext) - .crossfade(true) - .okHttpClient { - OkHttpClient.Builder() - .cache(CoilUtils.createDefaultCache(applicationContext)) - .build() - } - .build() - } -} -``` - -- **Or** calling `Coil.setImageLoader`: - -```kotlin -val imageLoader = ImageLoader.Builder(context) - .crossfade(true) - .okHttpClient { - OkHttpClient.Builder() - .cache(CoilUtils.createDefaultCache(context)) - .build() - } - .build() -Coil.setImageLoader(imageLoader) -``` - -The singleton `ImageLoader` can be retrieved using the `Context.imageLoader` extension function: - -```kotlin -val imageLoader = context.imageLoader -``` - -Setting the singleton `ImageLoader` is optional. If you don't set one, Coil will lazily create an `ImageLoader` with the default values. - -If you're using the `io.coil-kt:coil-base` artifact, you should create your own `ImageLoader` instance(s) and inject them throughout your app with dependency injection. [Read more about dependency injection here](../image_loaders/#singleton-vs-dependency-injection). - -!!! Note - If you set a custom `OkHttpClient`, you must set a `cache` implementation or the `ImageLoader` will have no disk cache. A default Coil cache instance can be created using [`CoilUtils.createDefaultCache`](../api/coil-base/coil.util/-coil-utils/create-default-cache.html). - ## ImageView Extension Functions -The `io.coil-kt:coil` artifact provides a set of type-safe `ImageView` extension functions. Here's an example for loading a URL into an `ImageView`: +The `io.coil-kt:coil` artifact provides a set of `ImageView` extension functions. Here's an example for loading a URL into an `ImageView`: ```kotlin imageView.load("https://www.example.com/image.jpg") @@ -235,42 +162,3 @@ val disposable = imageView.load("https://www.example.com/image.jpg") // Cancel the request. disposable.dispose() ``` - -## Memory Cache - -Each `ImageLoader` has its own `MemoryCache` of recently loaded images. To read/write a `Bitmap` to the memory cache, you need a `MemoryCache.Key`. There are two ways to get a `MemoryCache.Key`: - -- Create a `MemoryCache.Key` using its `String` constructor: `MemoryCache.Key("my_cache_key")` -- Get the `MemoryCache.Key` from an executed request: - -```kotlin -// If using the ImageLoader singleton -val memoryCacheKey = imageView.metadata.memoryCacheKey - -// Enqueue -val request = ImageRequest.Builder(context) - .data("https://www.example.com/image.jpg") - .target(imageView) - .listener { _, metadata -> - val memoryCacheKey = metadata.memoryCacheKey - } - .build() -imageLoader.enqueue(request) - -// Execute -val request = ImageRequest.Builder(context) - .data("https://www.example.com/image.jpg") - .build() -val result = imageLoader.execute(request) as SuccessResult -val memoryCacheKey = result.metadata.memoryCacheKey -``` - -Once you have the memory cache key, you can read/write to the memory cache synchronously: - -```kotlin -// Get -val bitmap: Bitmap? = imageLoader.memoryCache[memoryCacheKey] - -// Set -imageLoader.memoryCache[memoryCacheKey] = bitmap -``` diff --git a/docs/image_loaders.md b/docs/image_loaders.md index 755bb91983..97ff6fbcba 100644 --- a/docs/image_loaders.md +++ b/docs/image_loaders.md @@ -4,89 +4,88 @@ ```kotlin val imageLoader = ImageLoader.Builder(context) - .availableMemoryPercentage(0.25) .crossfade(true) .build() ``` -Coil performs best when you create a single `ImageLoader` and share it throughout your app. This is because each `ImageLoader` has its own memory cache, bitmap pool, and network observer. - -It's recommended, though not required, to call [`shutdown`](../api/coil-base/coil/-image-loader/shutdown.html) when you've finished using an image loader. This preemptively frees its memory and cleans up any observers. If you only create and use one `ImageLoader`, you do not need to shut it down as it will be freed when your app is killed. +Coil performs best when you create a single `ImageLoader` and share it throughout your app. This is because each `ImageLoader` has its own memory cache, disk cache, and `OkHttpClient`. ## Caching -Each `ImageLoader` keeps a memory cache of recently decoded `Bitmap`s as well as a reusable pool of `Bitmap`s to decode into. - -`ImageLoader`s rely on an `OkHttpClient` to handle disk caching. **By default, every `ImageLoader` is already set up for disk caching** and will set a max cache size of between 10-250MB depending on the remaining space on the user's device. - -However, if you set a custom `OkHttpClient`, you'll need to add the disk cache yourself. To get a `Cache` instance that's optimized for Coil, you can use [`CoilUtils.createDefaultCache`](../api/coil-base/coil.util/-coil-utils/create-default-cache.html). Optionally, you can create your own `Cache` instance with a different size + location. Here's an example: +Each `ImageLoader` keeps a memory cache of recently decoded `Bitmap`s as well as a disk cache for any images loaded from the Internet. Both can be configured when creating an `ImageLoader`: ```kotlin val imageLoader = ImageLoader.Builder(context) - .okHttpClient { - OkHttpClient.Builder() - .cache(CoilUtils.createDefaultCache(context)) + .memoryCache { + MemoryCache.Builder(context) + .maxSizePercent(0.25) + .build() + } + .diskCache { + DiskCache.Builder(context) + .directory(context.cacheDir.resolve("image_cache")) + .maxSizePercent(0.02) .build() } .build() ``` +You can access items in the memory and disk caches using their keys, which are returned in an `ImageResult` after an image is loaded. The `ImageResult` is returned by `ImageLoader.execute` or in `ImageRequest.Listener.onSuccess` and `ImageRequest.Listener.onError`. + +!!! Note + Coil 1.x relied on OkHttp's disk cache. Coil 2.x has its own disk cache and **should not** use OkHttp's `Cache`. + ## Singleton vs. Dependency Injection -Coil performs best when you create a single `ImageLoader` and share it throughout your app. This is because each `ImageLoader` has its own memory cache and bitmap pool. +The default Coil artifact (`io.coil-kt:coil`) includes the singleton `ImageLoader`, which can be accessed using an extension function: `context.imageLoader`. -If you use a dependency injector like [Dagger](https://github.com/google/dagger), then you should create a single `ImageLoader` instance and inject it throughout your app. +Coil performs best when you have a single `ImageLoader` that's shared throughout your app. This is because each `ImageLoader` has its own set of resources. -However, if you'd prefer a singleton the `io.coil-kt:coil` artifact provides a singleton `ImageLoader` instance that can be accessed using the `Context.imageLoader` extension function. [Read here](../getting_started/#singleton) for how to initialize the singleton `ImageLoader` instance. +The singleton `ImageLoader` can be configured by implementing `ImageLoaderFactory` on your `Application` class. -!!! Note - Use the `io.coil-kt:coil-base` artifact if you are using dependency injection. +Optionally, you can create your own `ImageLoader` instance(s) and inject them using a dependency injector like [Dagger](https://github.com/google/dagger). If you do that, depend on `io.coil-kt:coil-base` as that artifact doesn't create the singleton `ImageLoader`. ## Testing `ImageLoader` is an interface, which you can replace with a fake implementation. -For instance, you could inject a fake `ImageLoader` implementation which always returns the same `Drawable` synchronously: +For instance, you could create a fake `ImageLoader` implementation which always returns the same `Drawable` synchronously: ```kotlin -val fakeImageLoader = object : ImageLoader { - - private val disposable = object : Disposable { - override val isDisposed get() = true - override fun dispose() {} - override suspend fun await() {} - } +class FakeImageLoader(private val context: Context) : ImageLoader { override val defaults = DefaultRequestOptions() - - // Optionally, you can add a custom fake memory cache implementation. - override val memoryCache get() = throw UnsupportedOperationException() - - override val bitmapPool = BitmapPool(0) + override val components = ComponentRegistry() + override val memoryCache get() = null + override val diskCache get() = null override fun enqueue(request: ImageRequest): Disposable { // Always call onStart before onSuccess. - request.target?.onStart(placeholder = ColorDrawable(Color.BLACK)) - request.target?.onSuccess(result = ColorDrawable(Color.BLACK)) - return disposable + request.target?.onStart(request.placeholder) + val result = ColorDrawable(Color.BLACK) + request.target?.onSuccess(result) + return object : Disposable { + override val job = CompletableDeferred(newResult(request, result)) + override val isDisposed get() = true + override fun dispose() {} + } } override suspend fun execute(request: ImageRequest): ImageResult { + return newResult(request, ColorDrawable(Color.BLACK)) + } + + private fun newResult(request: ImageRequest, drawable: Drawable): SuccessResult { return SuccessResult( - drawable = ColorDrawable(Color.BLACK), + drawable = drawable, request = request, - metadata = ImageResult.Metadata( - memoryCacheKey = MemoryCache.Key(""), - isSampled = false, - dataSource = DataSource.MEMORY_CACHE, - isPlaceholderMemoryCacheKeyPresent = false - ) + dataSource = DataSource.MEMORY_CACHE ) } + override fun newBuilder() = throw UnsupportedOperationException() + override fun shutdown() {} - - override fun newBuilder() = ImageLoader.Builder(context) } ``` diff --git a/docs/image_pipeline.md b/docs/image_pipeline.md index 9946c77b77..e0bced3131 100644 --- a/docs/image_pipeline.md +++ b/docs/image_pipeline.md @@ -1,18 +1,19 @@ # Extending the Image Pipeline -Android supports many [image formats](https://developer.android.com/guide/topics/media/media-formats#image-formats) out of the box, however there are also plenty of formats it does not (e.g. GIF, SVG, TIFF, etc.) +Android supports many [image formats](https://developer.android.com/guide/topics/media/media-formats#image-formats) out of the box, however there are also plenty of formats it does not (e.g. GIF, SVG, MP4, etc.) -Fortunately, [ImageLoader](image_loaders.md)s support pluggable components to add new cache layers, new data types, new fetching behavior, new image encodings, or otherwise overwrite the base image loading behavior. Coil's image pipeline consists of four main parts: [Interceptors](../api/coil-base/coil.intercept/-interceptor), [Mappers](../api/coil-base/coil.map/-mapper), [Fetchers](../api/coil-base/coil.fetch/-fetcher), and [Decoders](../api/coil-base/coil.decode/-decoder). +Fortunately, [ImageLoader](image_loaders.md)s support pluggable components to add new cache layers, new data types, new fetching behavior, new image encodings, or otherwise overwrite the base image loading behavior. Coil's image pipeline consists of five main parts that are executed in the following order: [Interceptors](../api/coil-base/coil.intercept/-interceptor), [Mappers](../api/coil-base/coil.map/-mapper), [Keyers](../api/coil-base/coil.key/-keyer), [Fetchers](../api/coil-base/coil.fetch/-fetcher), and [Decoders](../api/coil-base/coil.decode/-decoder). Custom components must be added to the `ImageLoader` when constructing it through its [ComponentRegistry](../api/coil-base/coil/-component-registry): ```kotlin val imageLoader = ImageLoader.Builder(context) - .componentRegistry { + .components { add(CustomCacheInterceptor()) add(ItemMapper()) - add(CronetFetcher()) - add(GifDecoder()) + add(HttpUrlKeyer()) + add(CronetFetcher.Factory()) + add(GifDecoder.Factory()) } .build() ``` @@ -33,7 +34,7 @@ class CustomCacheInterceptor( return SuccessResult( drawable = value.bitmap.toDrawable(context), request = chain.request, - metadata = TODO() + dataSource = DataSource.MEMORY_CACHE ) } return chain.proceed(chain.request) @@ -62,7 +63,7 @@ We could write a custom mapper to map it to its URL, which will be handled later ```kotlin class ItemMapper : Mapper { - override fun map(data: Item) = data.imageUrl + override fun map(data: Item, options: Options) = data.imageUrl } ``` @@ -78,17 +79,20 @@ imageLoader.enqueue(request) See [Mapper](../api/coil-base/coil.map/-mapper) for more information. +## Keyers + +Keyers convert data into a portion of a cache key. This value is used as `MemoryCache.Key.key` when/if this request's output is written to the `MemoryCache`. + +See [Keyers](../api/coil-base/coil.key/-keyer) for more information. + ## Fetchers -Fetchers translate data into either a `BufferedSource` or a `Drawable`. +Fetchers translate data (e.g. URL, URI, File, etc.) into either an `ImageSource` or a `Drawable`. They typically convert the input data into a format that can then be consumed by a `Decoder`. Use this interface to add support for custom fetching mechanisms (e.g. Cronet, custom URI schemes, etc.) See [Fetcher](../api/coil-base/coil.fetch/-fetcher) for more information. ## Decoders -Decoders read a `BufferedSource` as input and return a `Drawable`. Use this interface to add support for custom file formats (e.g. GIF, SVG, TIFF, etc.). +Decoders read an `ImageSource` and return a `Drawable`. Use this interface to add support for custom file formats (e.g. GIF, SVG, TIFF, etc.). See [Decoder](../api/coil-base/coil.decode/-decoder) for more information. - -!!! Note - Decoders are responsible for closing the `BufferedSource` when finished. This allows custom decoders to return a `Drawable` while still reading the source. This can be useful to support file types such as [progressive JPEG](https://www.liquidweb.com/kb/what-is-a-progressive-jpeg/) where there is incremental information to show. diff --git a/docs/java_compatibility.md b/docs/java_compatibility.md index bc224f45c1..3abf64485a 100644 --- a/docs/java_compatibility.md +++ b/docs/java_compatibility.md @@ -22,7 +22,7 @@ imageLoader.enqueue(request) ``` !!! Note - `ImageView.load` extension functions cannot be used from Java. Use the `ImageRequest.Builder` API instead. + `ImageView.load` cannot be used from Java. Use the `ImageRequest.Builder` API instead. `suspend` functions cannot be easily called from Java. Thus, to get an image synchronously you'll have to use the `ImageLoader.executeBlocking` extension function which can be called from Java like so: diff --git a/docs/recipes.md b/docs/recipes.md index 49deec7d4a..0ddd8e6b84 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -8,116 +8,40 @@ See a common use case that isn't covered? Feel free to submit a PR with a new se [Palette](https://developer.android.com/training/material/palette-colors?hl=en) allows you to extract prominent colors from an image. To create a `Palette`, you'll need access to an image's `Bitmap`. This can be done in a number of ways: -#### Enqueue - -You can get access to an image's bitmap by setting a `Target` and enqueuing `ImageRequest`: - -```kotlin -val request = ImageRequest.Builder(context) - .data("https://www.example.com/image.jpg") - .allowHardware(false) // Disable hardware bitmaps. - .target { drawable -> - // Generate the Palette on a background thread. - val task = Palette.Builder(drawable.toBitmap()).generate { palette -> - // Consume the palette. - } - } - .build() -val disposable = imageLoader.enqueue(request) -``` - -#### Execute - -You can also execute an `ImageRequest`, which returns the drawable imperatively: +You can get access to an image's bitmap by setting a `ImageRequest.Listener` and enqueuing an `ImageRequest`: ```kotlin -val request = ImageRequest.Builder(context) - .data("https://www.example.com/image.jpg") - .allowHardware(false) // Disable hardware bitmaps. - .build() -val drawable = (imageLoader.execute(request) as SuccessResult).drawable - -val palette = coroutineScope { - launch(Dispatchers.IO) { - Palette.Builder(drawable.toBitmap()).generate() - } -} -``` - -#### Transition - -There may be cases where you want to load an image into a `PoolableViewTarget` (e.g. `ImageViewTarget`) while extracting the image's colors in parallel. For these cases, you can use a custom [`Transition`](transitions.md) to get access to the underlying bitmap: - -```kotlin -class PaletteTransition( - private val delegate: Transition?, - private val onGenerated: (Palette) -> Unit -) : Transition { - - override suspend fun transition(target: TransitionTarget, result: RequestResult) { - // Execute the delegate transition. - val delegateJob = delegate?.let { delegate -> - coroutineScope { - launch(Dispatchers.Main.immediate) { - delegate.transition(target, result) - } - } - } - - // Compute the palette on a background thread. - if (result is SuccessResult) { - val bitmap = result.drawable.toBitmap() - val palette = withContext(Dispatchers.IO) { - Palette.Builder(bitmap).generate() - } - onGenerated(palette) - } - - delegateJob?.join() - } -} - -// ImageRequest -val request = ImageRequest.Builder(context) - .data("https://www.example.com/image.jpg") - .allowHardware(false) // Disable hardware bitmaps. - .transition(PaletteTransition(CrossfadeTransition()) { palette -> - // Consume the palette. - }) - .target(imageView) - .build() -imageLoader.enqueue(request) - -// ImageView.load imageView.load("https://www.example.com/image.jpg") { + // Disable hardware bitmaps as Palette needs to read the image's pixels. allowHardware(false) - transition(PaletteTransition(CrossfadeTransition()) { palette -> - // Consume the palette. - }) + listener( + onSuccess = { _, result -> + // Create the palette on a background thread. + Palette.Builder(result.drawable.toBitmap()).generate { palette -> + // Consume the palette. + } + } + ) } ``` -!!! Note - You should not pass the drawable outside the scope of `Transition.transition`. This can cause the drawable's underlying bitmap to be pooled while it is still in use, which can result in rendering issues and crashes. - ## Using a custom OkHttpClient -Coil uses [`OkHttp`](https://github.com/square/okhttp/) for all its networking operations. You can specify a custom `OkHttpClient` when creating your `ImageLoader`: +Coil uses [OkHttp](https://github.com/square/okhttp/) for all its networking operations. You can specify a custom `OkHttpClient` when creating your `ImageLoader`: ```kotlin val imageLoader = ImageLoader.Builder(context) // Create the OkHttpClient inside a lambda so it will be initialized lazily on a background thread. .okHttpClient { OkHttpClient.Builder() - // You need to set the cache for disk caching to work. - .cache(CoilUtils.createDefaultCache(context)) + .addInterceptor(CustomInterceptor()) .build() } .build() ``` !!! Note - If you already have a built `OkHttpClient`, use [`newBuilder()`](https://square.github.io/okhttp/4.x/okhttp/okhttp3/-http-url/new-builder/) to build a new client that shares resources with the original. Also, it's recommended to use a separate `Cache` instance for your Coil `OkHttpClient`. Image files can quickly evict more important cached network responses if they share the same cache. + If you already have a built `OkHttpClient`, use [`newBuilder()`](https://square.github.io/okhttp/4.x/okhttp/okhttp3/-http-url/new-builder/) to build a new client that shares resources with the original. #### Headers @@ -149,7 +73,6 @@ class ResponseHeaderInterceptor( val imageLoader = ImageLoader.Builder(context) .okHttpClient { OkHttpClient.Builder() - .cache(CoilUtils.createDefaultCache(context)) // This header will be added to every image request. .addNetworkInterceptor(ResponseHeaderInterceptor("Cache-Control", "max-age=31536000,public")) .build() @@ -177,9 +100,9 @@ To achieve this effect, use the `MemoryCache.Key` of the first request as the `I // First request listImageView.load("https://www.example.com/image.jpg") -// Second request +// Second request (once the first request finishes) detailImageView.load("https://www.example.com/image.jpg") { - placeholderMemoryCacheKey(listImageView.metadata.memoryCacheKey) + placeholderMemoryCacheKey(listImageView.result.memoryCacheKey) } ``` @@ -224,10 +147,9 @@ class RemoteViewsTarget( Then `enqueue`/`execute` the request like normal: ```kotlin -val target = RemoteViewsTarget(...) val request = ImageRequest.Builder(context) .data("https://www.example.com/image.jpg") - .target(target) + .target(RemoteViewsTarget(...)) .build() imageLoader.enqueue(request) ``` diff --git a/docs/targets.md b/docs/targets.md index 9b0e0a9383..f81e01d116 100644 --- a/docs/targets.md +++ b/docs/targets.md @@ -1,6 +1,6 @@ # Targets -Targets receive the result of an `ImageRequest`. They often act as "view adapters" by taking the placeholder/error/success drawables and applying them to a `View` (e.g. `ImageViewTarget`). +Targets receive the `Drawable` result of an `ImageRequest`. They often act as "view adapters" by taking the placeholder/error/success drawables and applying them to a `View` (e.g. `ImageViewTarget`). Here's the easiest way to create a custom target: @@ -22,8 +22,7 @@ val request = ImageRequest.Builder(context) imageLoader.enqueue(request) ``` -There are 3 types of targets: +There are 2 types of targets: * [Target](../api/coil-base/coil.target/-target/): The base target class. Prefer this if the image request isn't tied to a `View`. * [ViewTarget](../api/coil-base/coil.target/-view-target/): A target with an associated `View`. Prefer this if the request sets the placeholder/error/success Drawables on a `View`. Using `ViewTarget` also binds the request to the `View`'s lifecycle. -* [PoolableViewTarget](../api/coil-base/coil.target/-poolable-view-target/): A `ViewTarget` that supports [bitmap pooling](../getting_started/#bitmap-pooling). This has performance benefits, however it comes with several strict behavior requirements. Read the docs for more information. diff --git a/docs/transformations.md b/docs/transformations.md index dfcb1cdf66..6c472fb65b 100644 --- a/docs/transformations.md +++ b/docs/transformations.md @@ -2,11 +2,11 @@ Transformations allow you to modify the pixel data of an image before the `Drawable` is returned from the request. -By default, Coil comes packaged with 4 transformations: [blur](../api/coil-base/coil.transform/-blur-transformation/), [circle crop](../api/coil-base/coil.transform/-circle-crop-transformation/), and [grayscale](../api/coil-base/coil.transform/-grayscale-transformation/), and [rounded corners](../api/coil-base/coil.transform/-rounded-corners-transformation/). +By default, Coil comes packaged with 2 transformations: [circle crop](../api/coil-base/coil.transform/-circle-crop-transformation/) and [rounded corners](../api/coil-base/coil.transform/-rounded-corners-transformation/). Transformations only modify the pixel data for static images. Adding a transformation to an `ImageRequest` that produces an animated image will convert it to a static image so the transformation can be applied. To transform the pixel data of each frame of an animated image, see [AnimatedTransformation](../api/coil-gif/coil-gif/coil.transform/-animated-transformation/). -See the [API doc](../api/coil-base/coil.transform/-transformation/) for more information. +See the [API documentation](../api/coil-base/coil.transform/-transformation/) for more information. !!! Note - If the `Drawable` returned by the image pipeline is not a `BitmapDrawable`, it will be converted to one. This will cause animated drawables to only draw the first frame of their animation. + If the `Drawable` returned by the image pipeline is not a `BitmapDrawable`, it will be converted to one. This will cause animated drawables to only draw the first frame of their animation. This behaviour can be disabled by setting `ImageRequest.Builder.allowConversionToBitmap(false)`. diff --git a/docs/transitions.md b/docs/transitions.md index 9cb5617f80..2dfa5cb71c 100644 --- a/docs/transitions.md +++ b/docs/transitions.md @@ -2,13 +2,13 @@ Transitions allow you to animate setting the result of an image request on a `Target`. -Both `ImageLoader` and `ImageRequest` builders accept a `Transition`. Transitions allow you to control how the sucess/error drawable is set on the `Target`. This allows you to animate the target's view or wrap the input drawable. +Both `ImageLoader` and `ImageRequest` builders accept a `Transition.Factory`. Transitions allow you to control how the sucess/error drawable is set on the `Target`. This allows you to animate the target's view or wrap the input drawable. By default, Coil comes packaged with 2 transitions: -- [`CrossfadeTransition`](../api/coil-base/coil.transition/-crossfade-transition/) which crossfades from the current drawable to the success/error drawable. -- [`Transition.NONE`](../api/coil-base/coil.transition/-transition/-companion/-n-o-n-e.html) which sets the drawable on the `Target` immediately without animating. +- [`CrossfadeTransition.Factory`](../api/coil-base/coil.transition/-crossfade-transition/) which crossfades from the current drawable to the success/error drawable. +- [`Transition.Factory.NONE`](../api/coil-base/coil.transition/-transition/-companion/-n-o-n-e.html) which sets the drawable on the `Target` immediately without animating. Take a look at the [`CrossfadeTransition` source code](https://github.com/coil-kt/coil/blob/main/coil-base/src/main/java/coil/transition/CrossfadeTransition.kt) for an example of how to write a custom `Transition`. -See the [API doc](../api/coil-base/coil.transition/-transition/) for more information. +See the [API documentation](../api/coil-base/coil.transition/-transition/) for more information. diff --git a/gradle.properties b/gradle.properties index 1aa1de6fcd..648e07ff03 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ compileSdk=31 # Maven GROUP=io.coil-kt -VERSION_NAME=2.0.0-SNAPSHOT +VERSION_NAME=2.0.0-rc01 POM_DESCRIPTION=An image loading library for Android backed by Kotlin Coroutines. POM_INCEPTION_YEAR=2019