본문 바로가기

Android/Jetpack Compose

[Compose] Coil을 사용하여 벡터파일인 SVG Image을 불러오는 방법. (feat. GIF Image)

728x90

개발을 하면서 이미지 파일을 사용하다 보면 어렵지 않게 SVG 파일을 사용할 수 있다.

SVG는 Scalable Vector Graphics의 약자로 2 차원 벡터 그래픽을 표현하기 위한 XML 기반의 파일 형식이다.

JPEG, PNG와 함께 자주 사용되는 이미지 파일 형태로, SVG는 특히 해상도를 대응해야 하는 복잡하지 않은 이미지 파일, 아이콘 등에 자주 사용한다.

 

필자도 실무를 진행하면서 SVG 파일을 자주 사용해왔는데, 생각해 보면 이번 이슈를 만나기 전까지는 SVG 파일은 resource file로 로컬에 저장해 두고 불러오는 형식으로만 사용을 했었다는 것을 알 수 있었다.

지금까지 Image load library로는 glide만 사용하고 있었는데, Glide에서는 별도의 작업을 해주지 않으면 SVG 파일을 읽어올 수 없기 때문이다.

 

따라서,

이번 글에서는 Coil을 사용하는 방법과 SVG Image를 읽어오는 방법에 대해서 작성해보고자 한다.


우선,

Glide를 사용해서 SVG Image를 불러오는 경우에는 별다른 에러는 발생하지 않지만 원하는 이미지가 제대로 보이지 않는 것을 쉽게 확인할 수 있다.

해당 문제를 해결하기 위해 여러가지로 찾아보았을 때, 3가지 해결 방법이 있었다.

 

  1. Glide 기반으로 만들어진 library를 사용하여 SVG를 읽어온다.
  2. Glide에서 만들어준 샘플 코드를 사용해 SVG를 읽어올 수 있도록 코드를 추가한다.
  3. Coil을 사용한다.

 

우선,

1번의 해결 방법은 GlideToVectorYou 라는 라이브러리로 stackOverFlow에서 Glide를 사용한 SVG Load를 검색하면 자주 보였던 라이브러리이다.

 

필자가 해당 라이브러리를 사용하지 않은 이유는, 19년도 이후 업데이트가 되지 않았기 때문에 실제 운영 중인 서비스에 해당 라이브러리를 넣는 것은 리스크가 크다고 판단하였다.

해당 github에 들어가 보면 사용하는 방법도 상당히 간단한 것으로 보이고, 부가적인 기능도 제공해 주기 때문에 리스크를 감당할 수 있다면 해당 라이브러리를 사용해도 괜찮을 것 같다고 생각했다.

 

2번째의 해결 방안은 다른 방법이 없을 때 최후에 선택할 방법이라고 생각하고 있었기 때문에 해당 케이스는 자세히 찾아보지 않았는데, 해당 깃허브에서 확인할 수 있듯이 SvgDecoder를 직접 구현하여 사용하는 것으로 보였다.

 

필자는 2번째의 방법을 사용하기 전에 다른 Image Loader 라이브러리를 사용해 보는 것도 좋을 것이라고 생각하여 Coil을 찾아보았고, 결과적으로 Coil을 사용하여 SVG Image File을 읽어올 수 있도록 수정하게 되었다.

 

Coil을 찾아보고 적용하게 된 이유는 안드로이드 Developer 페이지의 Loading Image 탭에서 Glide와 더불어 Coil에 대해서도 나와있었기 때문이다.

 

 

 

Coil에 대해서는 이전부터 알고 있기는 했지만, 코루틴에서 지원하는 라이브러리라고 하니 흥미가 생겨서 찾아본 게 가장 큰 것 같다.

 


그럼 Coil을 적용하고, 사용하는 방법에 대해서 알아보도록 하자.

 

implementation "io.coil-kt:coil-compose:$versions.coil"

 

가볍게 라이브러리를 Gradle에 추가해 주도록 하자.

 

현재 최신 버전은 v2.5.0까지 나왔지만, 필자가 사용하는 Kotlin 버전 때문에 최신 버전은 사용하지 못하는 관계로, 2.4.0 버전으로 사용하도록 하였다.

버전에 대한 정보는 이곳에서 확인해 보면 된다.

 

해당 라이브러리를 추가해 주면, Compose 환경에서 아주 쉽게 Image Load를 할 수 있다.

 

GlideImage(
    model = defaultIcon,
    contentDescription = null,
    contentScale = ContentScale.Crop
)

 

 

기존의 Glide를 이렇게 사용했다면,

 

AsyncImage(
    model = defaultIcon,
    contentDescription = null,
    contentScale = ContentScale.Crop,
)

 

GlideImage라고 선언된 부분을 AsyncImage로 변경하기만 하면 기본적으로 사용이 가능하다.

 

model에 들어가는 부분이 Glide와 마찬가지로 읽어올 이미지에 대한 정보가 들어갈 수 있는데, Any? 타입으로 Image Url이 들어가도 되고, asset file로 가지고 있는 이미지 리소스 파일을 넣어주어도 괜찮다.

 

그렇다면, 이렇게만 선언해서 Coil을 사용하면 SVG 파일을 사용할 수 있는가?

 

당연히, 아니다.

 

SVG 파일은 일반적인 Image 파일과는 다르게 벡터로 구현이 되어있기 때문에 그에 맞춰서 디코딩을 하지 않으면 정상적으로 읽어올 수 없다.

따라서, 한 가지 더 추가해 주도록 한다.

 

implementation "io.coil-kt:coil-svg:$versions.coil"

 

이름만 봐도 알 수 있듯이 , coil에서 svg를 위한 무언가가 들어가 있을 것이라고 상상할 수 있는 이름이다.

해당 라이브러리를 추가하고, model에 svg File을 Decode 할 수 있도록 무언가를 더 추가해 주도록 한다.

 

우선, Coil 공식 문서를 확인해 보면 다음과 같이 나와있다.

 

model can either be the ImageRequest.data value - or the ImageRequest itself. 
contentDescription sets the text used by accessibility services to describe what this image represents.

 

Model에 들어가는 값은 ImageRequest.data 혹은 ImageRequest 자체가 될 수 있다고 한다.

 

그리고, 사용 예제가 나와 있고

 

AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data("https://example.com/image.jpg")
        .crossfade(true)
        .build(),
    placeholder = painterResource(R.drawable.placeholder),
    contentDescription = stringResource(R.string.description),
    contentScale = ContentScale.Crop,
    modifier = Modifier.clip(CircleShape)
)

 

 

더불어 SVGs 탭을 확인해 보면 다음과 같이 나와있다.

 

 

val imageLoader = ImageLoader.Builder(context)
    .components {
        add(SvgDecoder.Factory())
    }
    .build()

 

jetpack Compose의 예제로는 추가되어있지 않지만,
이와 같이 svg 파일을 읽어오기 위해서는 SvgDecoder.Factory()를 선언해주어야 한다는 것을 알 수 있다.

 

어떻게 해당 Factory를 넣어주는가? 하고 생각할 수 있다.

그럴때는 일단 샘플을 넣어보고 .을 찍어 제공해주는 추가적인 함수를 확인해보면 된다.

 

 

이와 같이 DecoderFactory를 세팅해주는 함수를 지원하고 있음을 확인할 수 있다.

 

따라서, 이 두가지를 합치게 되면 다음과 같이 나오게 된다.

 

ImageRequest.Builder(LocalContext.current)
    .data(imageUrl)
    .decoderFactory(SvgDecoder.Factory())
    .build()

 

해당 ImageRequest를 AsyncImage의 model에 넣어주기만 하면 된다.

 

AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data(imageUrl)
        .decoderFactory(SvgDecoder.Factory())
        .build(),
    contentDescription = null,
    contentScale = contentScale
)

 

이와 같이 호출하게 되면 정상적으로 SVG 타입의 이미지 파일을 읽어와서 보여줄 수 있게 된다.


추가적으로, gif 이미지 파일에 대해서도 쉽게 읽어올 수 있다.

 

implementation "io.coil-kt:coil-gif:$versions.coil"

 

svg 때와 마찬가지로 이처럼 gif에 대한 라이브러리를 추가해주고,

 

val imageLoader = ImageLoader.Builder(context)
    .components {
        if (SDK_INT >= 28) {
            add(ImageDecoderDecoder.Factory())
        } else {
            add(GifDecoder.Factory())
        }
    }
    .build()

 

라고 문서에 적힌 것을 보면, compose에서는 해당 Factory를 svg와 마찬가지로 추가하면 된다고 이해할 수 있다.

 

즉,

ImageRequest.Builder(LocalContext.current)
    .data(imageUrl)
    .decoderFactory(
        if (SDK_INT >= 28) {
            ImageDecoderDecoder.Factory()
        } else {
            GifDecoder.Factory()
        }
    )
    .build()

 

해당 값을 model로 넣어주면 gif 파일도 정상적으로 읽어서 보여줄 수 있게 된다.


svg 파일을 읽어오기 위해 이것 저것을 찾아보다 coil을 적용해 보았는데, 생각보다 jetpack compose를 사용할 때 coil을 사용하는 것이 더 유틸이 좋다고 생각이 되었다.

공식 문서에서도 확인할 수 있듯이 glide와 coil은 각각 다른 장점을 갖고 있지만, compose 환경에서 glide를 사용하면서 아쉽다고 생각했던 기능들이 coil에서는 전부 지원하고 있어서 사용하는 것에 너무 편리했고, 추가적으로 작성했던 코드들을 제거할 수 있어서 좋았다.

 

물론 모든 경우에 coil을 도입한 것은 아니지만, 라이브러리에 대해 차차 더 깊게 파고들어 공부한 후에, 적절한 곳에 적절한 라이브러리를 사용하여 이미지를 로드함으로써 더 좋은 퍼포먼스를 낼 수 있는 앱을 만들어보고자 한다.

728x90