6

I am developing an android app using the Jetpack Compose with Coil ImageLoader library.

It shows a user's profile image.

I receive the profile data from the API. GET: /users/{userId}

The response contains userId and profileImgKey.

For the user profile image, Backend provides GET: /photo/{userId} API.

But the problem is that if some user update his/her profile image, other users still see the previous profile image not the new image.

Because it is cached by Coil.

If I turn-off the caching option, it may work fine. But I don't want to do it. I don't want to lose the performance benefit.

When the user update their profile image, the profileImgKey is changed. So I want to use this as a cache key.

But I don't know how to use this.

yoonhok
  • 2,575
  • 2
  • 30
  • 58

3 Answers3

11

In Coil 2.0.0 working with network cache is significantly simplified.

Specify diskCacheKey and memoryCacheKey in rememberAsyncImagePainter, also key is still needed to trigger recomposition.

val context = LocalContext.current
key(key) {
    Image(
        rememberAsyncImagePainter(
            remember(url) {
                ImageRequest.Builder(context)
                    .data(url)
                    .diskCacheKey(url)
                    .memoryCacheKey(url)
                    .build()
            }
        ),
        null
    )
}

And clear cache using image loader:

val imageLoader = context.imageLoader
imageLoader.diskCache?.remove(url)
imageLoader.memoryCache?.remove(MemoryCache.Key(url))

Answer for Coil 1.4.0

Coil has a cache on two levels:

  1. For network calls Coil uses OkHttp, to access its cache you need to create it manually as shown in documentation. I think in this case it's best to store both the cache and the image loader in the DI, but you can also create a composition local to access this cache from any composable:

    val LocalCoilHttpCache = staticCompositionLocalOf<Cache> {
        error("coilHttpCache not provided")
    }
    

    Provide it in your activity/fragment:

    val cache = CoilUtils.createDefaultCache(this)
    val imageLoader = ImageLoader.Builder(this)
        .okHttpClient {
            OkHttpClient.Builder()
                .cache(cache)
                .build()
        }
        .build()
    
    setContent {
        CompositionLocalProvider(
            LocalImageLoader provides imageLoader,
            LocalCoilHttpCache provides cache,
        ) {
            // your application
        }
    }
    

    Get it in any composable

    val httpCache = LocalCoilHttpCache.current
    

    Then, regardless of where you store it in the DI or in the composition local, you can clear the necessary cache with the following code:

    val urlIterator = httpCache.urls()
    while (urlIterator.hasNext()) {
        if (urlIterator.next() == urlToRemove) {
            urlIterator.remove()
        }
    }
    
  2. After downloading an image from the network, it is converted to Bitmap. These bitmaps are cached with Memory cache, so you need to clear that as well. The easiest way, according to the documentation, is to specify a cache key, e.g. URL:

    rememberImagePainter(
        url,
        builder = {
            memoryCacheKey(MemoryCache.Key(url))
        }
    ),
    

    Then you can clean it up:

    val loader = LocalImageLoader.current
    // or out of composable
    val loader = Coil.imageLoader(context)
    // ..
    loader.memoryCache.remove(MemoryCache.Key(url))
    

The last step is to force image recomposition. You can do this with key, specifying the value to change, in your case profileImgKey should work:

key(profileImgKey) {
    Image(
        rememberImagePainter(
            // ...
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • Great answer - works. I had done everything already in my code, before seeing the crucial important last step here with key(..) to trigger recomposition. – zaifrun Oct 17 '22 at 10:30
  • I was using `Uri`, and it expects String from me. How do I know what exactly it should be? – user924 Apr 07 '23 at 08:33
2

The easiest way to ensure that you get a fresh image, even when it changes is to append a query parameter to the url with a random value. For example:

someurl/user1.jpg?49610269

And done like this:

val imageUrl = "someUrl/user1.jpg?" + (0..1_000_000).random()

If your url already has query parameters, just add a new parameter that your url doesn't use.

Images in caches are reused whenever the url (including the query string) does not change. By changing the url, you force Coil to obtain the most recent image.

Johann
  • 27,536
  • 39
  • 165
  • 279
0

Inorder to load a new image not cached in coil try the following code on that particular Image View :

imgVw.load(imgUrl) {
    memoryCachePolicy(CachePolicy.DISABLED)
}
Mudit Goel
  • 196
  • 1
  • 5