Skip to content

Painter Builder with Coil

This guide explains the PainterBuilder utility class from the Fiori Compose library. It shows you how to configure Coil 3 as the ImageLoader used by the library. The PainterBuilder supports both Coil 2 and Coil 3. However, Coil 2 support is deprecated, so you should prefer Coil 3 for new apps.

Summary

  • The PainterBuilder class converts Fiori image/icon abstractions into Compose Painter instances.
  • You can work with a variety of resources: local resources, bitmaps, drawables, vector images, files, media URIs, and remote URLs.
  • Supports both Coil 2 and Coil 3. Coil 2 paths are retained for backward compatibility, but they are deprecated. To use Coil 3, call the Coil 3 setup near your app's entry point.

Key Functions in PainterBuilder

The PainterBuilder companion offers several composable helpers. Here are the most commonly used functions and their purposes:

  • @Composable fun build(icon: FioriIcon, placeholderResId: Int? = null, errorResId: Int? = null): Painter

    • You can convert a FioriIcon into a Compose Painter.
    • The software handles various icon types: RESOURCE, BITMAP, IMAGEVECTOR, PAINTER, DRAWABLE, and URL.
  • @Composable fun build(image: FioriImage, urlRequestListener: ImageRequest.Listener? = null, placeholderResId: Int? = null, errorResId: Int? = null): Painter

    • Convert a FioriImage into a Compose Painter.
    • You can handle various image types: RESOURCE, BITMAP, DRAWABLE, URL, and PAINTER.
    • URL loading uses Coil and accepts an optional ImageRequest.Listener. This is used for Coil 2 paths.
  • @Composable fun fetchMediaFileAsPainter(uri: Uri, isVideo: Boolean): Painter

    • You build a Painter from a media file Uri.
    • If the file is an image: build a painter from the image.
    • If the file is a video, the system builds the painter using the first video frame.
    • The isVideo parameter is specific to Coil 2. In Coil 3, the system automatically detects the media type.
  • @Composable fun buildCustomLogo(customLogo: CustomLogo): Painter

    • You convert a CustomLogo abstraction into a Painter.
    • You can handle various image types: RESOURCE, URL, FILE, DRAWABLE, PAINTER.
  • @Composable fun WrappedSubcomposeAsyncImage(...)

    • This is a wrapper for Coil's SubcomposeAsyncImage that supports both Coil 2 and Coil 3.
    • You can provide loading, onSuccess, and onError composables or handlers. The system internally selects either the Coil 2 or Coil 3 API based on whether the Coil 3 factory is available.

ImageLoader Setup Helpers (Static APIs)

  • setupCoil3ImageLoader(imageLoaderFactory: () -> coil3.ImageLoader)

    • Create a factory that returns a Coil 3 ImageLoader singleton. The PainterBuilder calls this factory whenever it needs the Coil 3 image loader.
    • The factory returns a singleton instance each time you call it.
  • @Deprecated setupImageLoader(context: Context, okHttpClient: OkHttpClient, isCacheEnabled: Boolean = true, memoryCachePercent: Double = 0.2)

    • This deprecated helper installs a custom Coil 2 ImageLoader globally. It uses the Coil API and an internal FioriImageLoaderFactory.
    • This feature is kept for backward compatibility.
  • @Deprecated setupCoil2ImageLoader(imageLoader: ImageLoader)

    • Deprecated: directly setting a custom Coil 2 ImageLoader instance.

How PainterBuilder Chooses Coil 2 vs Coil 3

  • If you call PainterBuilder.setupCoil3ImageLoader { ... } and the provided factory returns a Coil 3 singleton image loader, PainterBuilder uses Coil 3 APIs for URL, file, and media loading. It also uses SubcomposeAsyncImage.
  • If you don't provide a Coil 3 factory, PainterBuilder uses Coil 2 APIs, which are on a deprecated path. This ensures older apps continue to work without changes. However, you should migrate to Coil 3 as soon as possible.

Example: Setting up Coil 3

Place the Coil 3 setup near your app entry point, such as the root composable or Application. The demo app sets up a singleton Coil 3 ImageLoader factory using Compose's platform utilities. It then passes the singleton to PainterBuilder.

@Composable
private fun SetupCoil3() {
    // 1) Provide a Compose-level singleton ImageLoader factory used by Coil 3 Compose integration
    coil3.compose.setSingletonImageLoaderFactory { context ->
        coil3.ImageLoader.Builder(context)
            .crossfade(true)
            .components {
                // Optional: add custom fetchers/decoders, e.g. OkHttp network fetcher
                add(OkHttpNetworkFetcherFactory(callFactory = { OkHttpClient() }))
                // Use AnimatedImageDecoder on API 28+ for animated images, otherwise fallback to GifDecoder
                if (android.os.Build.VERSION.SDK_INT >= 28) {
                    add(AnimatedImageDecoder.Factory())
                } else {
                    add(GifDecoder.Factory())
                }
                // Add SVG and video decoders if you need them
                add(SvgDecoder.Factory())
                add(VideoFrameDecoder.Factory())
            }
            .build()
    }

    // 2) Supply the same Coil 3 singleton ImageLoader to Fiori Compose PainterBuilder
    val context = coil3.compose.LocalPlatformContext.current
    PainterBuilder.setupCoil3ImageLoader {
        // Return the singleton coil3 ImageLoader. The factory must return a singleton when called multiple times.
        coil3.SingletonImageLoader.get(context)
    }
}

Notes:

  • Call SetupCoil3() from your root composable. This ensures that the ImageLoader and PainterBuilder are ready before any image loading occurs.
  • The setSingletonImageLoaderFactory function serves as a Coil 3 helper. It registers a factory for ImageLoader that is aware of Compose.

Minimal Migration Notes (Coil 2 -> Coil 3)

  • PainterBuilder already supports both. You should call setupCoil3ImageLoader at app startup.
  • When you configure Coil 3, PainterBuilder uses Coil 3 APIs and decoders: SvgDecoder, VideoFrameDecoder, and AnimatedImageDecoder/GifDecoder.
  • If you used PainterBuilder.setupImageLoader in Coil 2, you can remove that call. Replace it with the Coil 3 setup shown above.

Edge Cases & Tips

  • Make sure you register the Coil 3 factory early in the root composable. This ensures that components using images at startup don't revert to the deprecated Coil 2.
  • Provide a singleton ImageLoader. Creating many ImageLoader instances wastes memory and resources.
  • When you load video frames, make sure you add VideoFrameDecoder.Factory() to the ImageLoader components.
  • To enable SVG support, add SvgDecoder.Factory() to the ImageLoader components. Both Coil 2 and Coil 3 paths include SVG decoders where needed.

External References

For more details on Coil 3 and its Compose integration, refer to the official documentation and API pages.


Last update: September 23, 2025