From a0be88fd727898d4626ca51876d0bfb4e50dcb77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateo=20Guzm=C3=A1n?= Date: Tue, 12 Nov 2024 10:44:09 -0800 Subject: [PATCH] feat(image): [android] adding `force-cache` cache control option (#47426) Summary: This PR follows up on https://github.com/facebook/react-native/issues/47182 and https://github.com/facebook/react-native/issues/47348 by adding `force-cache`, the final missing option to align caching controls with the existing behavior on iOS. Local caching behavior remains unchanged: if a cached image is available locally, it will be returned; otherwise, a network request will be made. When an image request is sent over the network, the `force-cache` option sent from the sent fJS side will now use the `okhttp3.CacheControl.FORCE_CACHE` directive. ## Changelog: [ANDROID] [ADDED] - Image `force-cache` caching control option Pull Request resolved: https://github.com/facebook/react-native/pull/47426 Test Plan: New example added to the RNTester under the cache policy examples. Then inspecting that the cache control is set correctly before sending it in the `okhttp3.Request` builder. ```kt FLog.w("ReactNative", "fetching uri: %s, with cacheControl: %s", uri, cacheControlBuilder.build().toString()) // fetching uri: https:...png?cacheBust=force-cache, with cacheControl: no-store, max-stale=2147483647, only-if-cached ``` This case was a bit more tricky to test in terms of e2e as it would involve some caching in the server as well, I'm open to suggestions to make this more complete. Reviewed By: javache Differential Revision: D65490360 Pulled By: Abbondanzo fbshipit-source-id: f807a9793f85caea39c59a370d057b9a1d450a78 --- .../react-native/Libraries/Image/ImageSource.d.ts | 2 -- .../react-native/Libraries/Image/ImageSource.js | 2 -- .../react-native/ReactAndroid/api/ReactAndroid.api | 1 + .../react/modules/fresco/ImageCacheControl.kt | 6 ++++++ .../modules/fresco/ReactOkHttpNetworkFetcher.kt | 14 ++++++++++---- .../facebook/react/views/image/ReactImageView.kt | 1 + .../rn-tester/js/examples/Image/ImageExample.js | 12 ++++++++++++ 7 files changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/react-native/Libraries/Image/ImageSource.d.ts b/packages/react-native/Libraries/Image/ImageSource.d.ts index 2d47b6ba194..406e89520e1 100644 --- a/packages/react-native/Libraries/Image/ImageSource.d.ts +++ b/packages/react-native/Libraries/Image/ImageSource.d.ts @@ -50,8 +50,6 @@ export interface ImageURISource { * its age or expiration date. If there is no existing data in the cache corresponding * to a URL load request, no attempt is made to load the data from the originating source, * and the load is considered to have failed. - * - * @platform ios (for `force-cache`) */ cache?: 'default' | 'reload' | 'force-cache' | 'only-if-cached' | undefined; /** diff --git a/packages/react-native/Libraries/Image/ImageSource.js b/packages/react-native/Libraries/Image/ImageSource.js index 1698c945b0b..71de1497ccc 100644 --- a/packages/react-native/Libraries/Image/ImageSource.js +++ b/packages/react-native/Libraries/Image/ImageSource.js @@ -65,8 +65,6 @@ export interface ImageURISource { * its age or expiration date. If there is no existing data in the cache corresponding * to a URL load request, no attempt is made to load the data from the originating source, * and the load is considered to have failed. - * - * @platform ios (for `force-cache`) */ +cache?: ?('default' | 'reload' | 'force-cache' | 'only-if-cached'); diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 88ad2d0823a..bf197be2bc6 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -3334,6 +3334,7 @@ public final class com/facebook/react/modules/fresco/FrescoModule$Companion { public final class com/facebook/react/modules/fresco/ImageCacheControl : java/lang/Enum { public static final field DEFAULT Lcom/facebook/react/modules/fresco/ImageCacheControl; + public static final field FORCE_CACHE Lcom/facebook/react/modules/fresco/ImageCacheControl; public static final field ONLY_IF_CACHED Lcom/facebook/react/modules/fresco/ImageCacheControl; public static final field RELOAD Lcom/facebook/react/modules/fresco/ImageCacheControl; public static fun getEntries ()Lkotlin/enums/EnumEntries; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ImageCacheControl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ImageCacheControl.kt index acb04ea7beb..ca25c3fc4bb 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ImageCacheControl.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ImageCacheControl.kt @@ -15,6 +15,12 @@ public enum class ImageCacheControl { * be used to satisfy a URL load request. */ RELOAD, + /** + * The existing cache data will be used to satisfy a request, regardless of its age or expiration + * date. If there is no existing data in the cache corresponding to a URL load request, the data + * is loaded from the originating source. + */ + FORCE_CACHE, /** * The existing cache data will be used to satisfy a request, regardless of its age or expiration * date. If there is no existing data in the cache corresponding to a URL load request, no attempt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactOkHttpNetworkFetcher.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactOkHttpNetworkFetcher.kt index 502f220a090..2e0bec6a119 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactOkHttpNetworkFetcher.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/ReactOkHttpNetworkFetcher.kt @@ -12,6 +12,7 @@ import com.facebook.imagepipeline.backends.okhttp3.OkHttpNetworkFetcher import com.facebook.imagepipeline.producers.NetworkFetcher import com.facebook.react.bridge.ReadableMap import com.facebook.react.modules.network.OkHttpCompat +import java.util.concurrent.TimeUnit import okhttp3.CacheControl import okhttp3.OkHttpClient import okhttp3.Request @@ -35,21 +36,26 @@ internal class ReactOkHttpNetworkFetcher(private val okHttpClient: OkHttpClient) fetchState.submitTime = SystemClock.elapsedRealtime() val uri = fetchState.uri var requestHeaders: Map? = null - val cacheControlBuilder = CacheControl.Builder().noStore() + val cacheControlBuilder = CacheControl.Builder() if (fetchState.context.imageRequest is ReactNetworkImageRequest) { val networkImageRequest = fetchState.context.imageRequest as ReactNetworkImageRequest requestHeaders = getHeaders(networkImageRequest.headers) when (networkImageRequest.cacheControl) { ImageCacheControl.RELOAD -> { - cacheControlBuilder.noCache() + cacheControlBuilder.noStore().noCache() + } + ImageCacheControl.FORCE_CACHE -> { + cacheControlBuilder.maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS) } ImageCacheControl.ONLY_IF_CACHED -> { - cacheControlBuilder.onlyIfCached() + cacheControlBuilder.onlyIfCached().maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS) } ImageCacheControl.DEFAULT -> { - // No-op + cacheControlBuilder.noStore() } } + } else { + cacheControlBuilder.noStore() } val headers = OkHttpCompat.getHeadersFromMap(requestHeaders) val request = diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt index 5365d689f5f..fc10007e7a7 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt @@ -312,6 +312,7 @@ public class ReactImageView( null, "default" -> ImageCacheControl.DEFAULT "reload" -> ImageCacheControl.RELOAD + "force-cache" -> ImageCacheControl.FORCE_CACHE "only-if-cached" -> ImageCacheControl.ONLY_IF_CACHED else -> ImageCacheControl.DEFAULT } diff --git a/packages/rn-tester/js/examples/Image/ImageExample.js b/packages/rn-tester/js/examples/Image/ImageExample.js index bbd0054bf78..f2f6adfdc09 100644 --- a/packages/rn-tester/js/examples/Image/ImageExample.js +++ b/packages/rn-tester/js/examples/Image/ImageExample.js @@ -666,6 +666,18 @@ function CacheControlAndroidExample(): React.Node { key={reload} /> + + Force-cache + console.log(e.nativeEvent.error)} + /> + Only-if-cached