Compose Camp 2: Mastering Images in Jetpack Compose with Coil-Compose and Landscapist

Compose Camp 2: Mastering Images in Jetpack Compose with Coil-Compose and Landscapist

Images and graphics add beauty and life to android apps. In android development, you will be required to design and load images and graphics into your application. Jetpack Compose has APIs and external libraries that helps you handle the visuals like images and graphics in your android app, from your logo to complex images and graphics. In this article, we will cover

  • How to design and customize images in Jetpack Compose

  • Image loading in compose

Design and Customize Images in Jetpack Compose

Jetpack compose makes it easy to design and customize images and graphics. Here is an example of how to create an image

@Composable
fun PosView
    Image(
        painter = painterResource(R.drawable.post_image),
        contentDescription = "Post image",
        modifier = Modifier.fillMaxSize()
    )
}

In the above code, you declare an Image that takes in painterResource, which loads the image from the drawable folder, has contentDescription of “Post Image” and occupies the available space using fillMaxSize modifier.

Sizing and Scaling

You can modify the size of the image above by adding size modifier.

@Composable
fun PosView(){
    Image(
        painter = painterResource(R.drawable.post_image),
        contentDescription = "Post image",
        modifier = Modifier
    .size(150.dp)
  )
}

You need to tell Jetpack Compose how to scale the image. Some of the common scale types are

  • Fit — scales the image uniformly inside the available space

  • FillWidth — image fills the available width

  • FillHeight — image fills the available height

  • Crop — crops image to fit destination size

  • Inside — image maintains aspect ratio inside destination

  • None — no scaling applied

You can apply scaling to the above image using the ContentScale as shown below.

@Composable
fun PosView(){
    Image(
        painter = painterResource(R.drawable.post_image),
        contentDescription = "Post image",
    contentScale = ContentScale.Crop
        modifier = Modifier
        // Change image size to 150 dp
    .size(150.dp)
    )
}

You can read more on scaling in the official documentation here.

Shape

You can modify the shape of image in Jetpack Compose to common shapes like Circle, Rectangle or Square with rounded corners and custom shapes.

Compose uses clip modifer to use shapes such as CircleShape, RoundedCornerShape.

Here, we will change the shape of the PostView to CircleShape

@Composable
fun PosView(){
    Image(
        painter = painterResource(R.drawable.post_image),
        contentDescription = "Post image",
    contentScale = ContentScale.Crop
        modifier = Modifier
    .size(150.dp)
        // Make the image circular
    .clip(CircleShape)
    )
}

When using RoundedCornerShape clip, ensure you declare the size of corner radius. For example

@Composable
fun PosView(){
    Image(
        painter = painterResource(R.drawable.post_image),
        contentDescription = "Post image",
    contentScale = ContentScale.Crop
        modifier = Modifier
    .size(150.dp)
        // Rounded corner shape with 8dp radius
    .clip(RoundedCornerShape(8.dp))
    )
}

When creating an image with custom shape, you use aspectRatio. Common image aspect ratios are

  • 1:1 — square image

  • 4:3 — standard aspect ratio

  • 16:9 wide image. Commonly used in widescreen

  • 3:2 — classic aspect ratio

@Composable
fun PosView(){
    Image(
        painter = painterResource(R.drawable.post_image),
        contentDescription = "Post image",
    contentScale = ContentScale.Crop
        modifier = Modifier
    .aspectRatio(16f / 9f)
    )
}

You can read more about image scaling and additional modifiers here

Loading Images

You have to load images from a source to your application, either local or remote. We have been loading images from the drawable folder in the above examples. You can also load images from the web.

In this section, we will look at two common libraries used in compose — landscapist and coil-compose

Coil Compose

Coil-compose is built on top of coil image loading library. It simplifies asynchronous loading of images using coroutines. To get started, add coil-compose dependency in your application. You can change the version to the latest version.

implementation("io.coil-kt:coil-compose:2.2.2")

We use AsycImage in Coil-Compose, and the above example changes as shown below.

@Composable
fun PosView(){
    AsyncImage(
    //This is an example image from Unspash.com
        model = "https://unsplash.com/photos/6xv4A1VA1rU",
        contentDescription = "Post image",
    contentScale = ContentScale.Crop
        modifier = Modifier
        .aspectRatio(16f / 9f)
    )
}

Coil provides additional modifications when loading images from the web. Here are some of the modifications you can apply to your app.

  • Model

This is an instance of ImageRequest, which defines the details of the image to be loaded. In this case, it specifies the URL of the image to be loaded from the internet and builds it to an image.

AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data("https://unsplash.com/photos/6xv4A1VA1rU")
        .build(),
    contentDescription = "Post image",
    contentScale = ContentScale.Crop,
    modifier = Modifier
    .aspectRatio(16f / 9f)
)
  • Placeholder

You can have placeholder images loading from the local storage, which will be displayed when the network image is loading or has failed.

AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data("https://unsplash.com/photos/6xv4A1VA1rU")
        .build(),
    contentDescription = "Post image",
    contentScale = ContentScale.Crop,
    placeholder = painterResource(id = R.drawable.loading_placeholder),  
    error = painterResource(id = R.drawable.image_error),
    modifier = Modifier
    .aspectRatio(16f / 9f)
)
  • Observe State

We will use AsyncImagePainter to observe state when loading images.

val asyncPainter = rememberAsyncImagePainter(
    model = ImageRequest.Builder(LocalContext.current)
        .data("https://unsplash.com/photos/6xv4A1VA1rU")
    .size(Size.ORIGINAL)
        .build(),
)
Image(
    painter = asyncPainter,
    contentDescription = "Post image"
    modifier = Modifier
    .aspectRatio(16f / 9f)
)
  • Crossfade

Coil Compose has an inbuilt crossfade modifier for transitions. Custom transition requires reference to the view, thus not available in AsyncImage and AsyncImagePainter we have used in the above example. You can add crossfade in the above code and achieve beautiful transitions.

val asyncPainter = rememberAsyncImagePainter(
    model = ImageRequest.Builder(LocalContext.current)
        .data("https://unsplash.com/photos/6xv4A1VA1rU")
    .size(Size.ORIGINAL)
    .crossfade(true)
        .build(),
)
Image(
    painter = asyncPainter,
    contentDescription = "Post image"
    modifier = Modifier
    .aspectRatio(16f / 9f)
)

You can read more about Coil-compose in the official documentation.

Landscapist

Landscapist is another library that simplifies image loading using popular image-loading libraries like Glide, Coil and Fresco.

  • Glide

Add landscapist-glide dependency in your grade build file.

implementation "com.github.skydoves:landscapist-glide:2.1.8"

To load image using glide, use the following

GlideImage(
  imageModel = { "<https://unsplash.com/photos/6xv4A1VA1rU>" }
  imageOptions = ImageOptions(
    contentScale = ContentScale.Crop,
    alignment = Alignment.Center
    modifier = Modifier
    .aspectRatio(16f / 9f)
  )
)
  • Coil

Add landscapist-coil dependency in your grade build file.

implementation "com.github.skydoves:``landscapist-coil:2.1.8"

To load image using glide, use the following

CoilImage(
  imageModel = { "<https://unsplash.com/photos/6xv4A1VA1rU>" }
  imageOptions = ImageOptions(
    contentScale = ContentScale.Crop,
    alignment = Alignment.Center
    modifier = Modifier
      .aspectRatio(16f / 9f)
  )
)
  • Fresco

Add landscapist-fresco dependency in your grade build file.

implementation "com.github.skydoves:``landscapist-fresco:2.1.8"

To load image using glide, use the following

CoilImage(
  imageModel = { "<https://unsplash.com/photos/6xv4A1VA1rU>" }
  imageOptions = ImageOptions(
    contentScale = ContentScale.Crop,
    alignment = Alignment.Center
        modifier = Modifier
          ko.aspectRatio(16f / 9f)
  )
)

To use Fresco, you need an additional configuration called pipeline.

class ImageApp : Application() {  
override fun onCreate() {
    super.onCreate()
val pipelineConfig =
      OkHttpImagePipelineConfigFactory
        .newBuilder(this, OkHttpClient.Builder().build())
        .setDiskCacheEnabled(true)
        .setDownsampleEnabled(true)
        .setResizeAndRotateEnabledForNetwork(true)
        .build()
    Fresco.initialize(this, pipelineConfig)
  }
}

Check the official documentation to learn more about customizing images and image-loading libraries in Jetpack Compose.