Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(image): enabling basic cache control for android #47182

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/react-native/Libraries/Image/ImageSource.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export interface ImageURISource {
* 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
* @platform ios (for `force-cache` and `only-if-cached`)
*/
cache?: 'default' | 'reload' | 'force-cache' | 'only-if-cached' | undefined;
/**
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native/Libraries/Image/ImageSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export interface ImageURISource {
* 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
* @platform ios (for `force-cache` and `only-if-cached`)
*/
+cache?: ?('default' | 'reload' | 'force-cache' | 'only-if-cached');

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.views.image

public enum class ImageCacheControl {
DEFAULT,
RELOAD,
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import android.graphics.drawable.Drawable
import android.net.Uri
import com.facebook.common.references.CloseableReference
import com.facebook.common.util.UriUtil
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.drawee.controller.AbstractDraweeControllerBuilder
import com.facebook.drawee.controller.ControllerListener
import com.facebook.drawee.controller.ForwardingControllerListener
Expand Down Expand Up @@ -268,7 +269,13 @@ public class ReactImageView(
} else if (sources.size() == 1) {
// Optimize for the case where we have just one uri, case in which we don't need the sizes
val source = sources.getMap(0)
var imageSource = ImageSource(context, source.getString("uri"))
var imageSource =
ImageSource(
context,
source.getString("uri"),
0.0,
0.0,
source.getString("cache"))
if (Uri.EMPTY == imageSource.uri) {
warnImageSource(source.getString("uri"))
imageSource = getTransparentBitmapImageSource(context)
Expand All @@ -282,7 +289,8 @@ public class ReactImageView(
context,
source.getString("uri"),
source.getDouble("width"),
source.getDouble("height"))
source.getDouble("height"),
source.getString("cache"))
if (Uri.EMPTY == imageSource.uri) {
warnImageSource(source.getString("uri"))
imageSource = getTransparentBitmapImageSource(context)
Expand Down Expand Up @@ -410,6 +418,7 @@ public class ReactImageView(

private fun maybeUpdateViewFromRequest(doResize: Boolean) {
val uri = this.imageSource?.uri ?: return
val cacheControl = this.imageSource?.cacheControl

val postprocessorList = mutableListOf<Postprocessor>()
iterativeBoxBlurPostProcessor?.let { postprocessorList.add(it) }
Expand All @@ -418,6 +427,12 @@ public class ReactImageView(

val resizeOptions = if (doResize) resizeOptions else null

if (cacheControl == ImageCacheControl.RELOAD) {
val imagePipeline = Fresco.getImagePipeline()

imagePipeline.evictFromCache(uri)
}

val imageRequestBuilder =
ImageRequestBuilder.newBuilderWithSource(uri)
.setPostprocessor(postprocessor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package com.facebook.react.views.imagehelper

import android.content.Context
import android.net.Uri
import com.facebook.react.views.image.ImageCacheControl
import java.util.Objects

/** Class describing an image source (network URI or resource) and size. */
Expand All @@ -19,7 +20,8 @@ constructor(
/** Get the source of this image, as it was passed to the constructor. */
public val source: String?,
width: Double = 0.0,
height: Double = 0.0
height: Double = 0.0,
cacheControl: String? = null
) {

/** Get the URI for this image - can be either a parsed network URI or a resource URI. */
Expand All @@ -30,6 +32,8 @@ constructor(
public open val isResource: Boolean
get() = _isResource

public val cacheControl: ImageCacheControl = computeCacheControl(cacheControl)

private var _isResource: Boolean = false

override fun equals(other: Any?): Boolean {
Expand Down Expand Up @@ -64,6 +68,17 @@ constructor(
return ResourceDrawableIdHelper.instance.getResourceDrawableUri(context, source)
}

private fun computeCacheControl(cacheControl: String?): ImageCacheControl {
return when (cacheControl) {
null,
"default" -> ImageCacheControl.DEFAULT
"reload" -> ImageCacheControl.RELOAD
else -> {
return ImageCacheControl.DEFAULT
}
}
}

public companion object {
private const val TRANSPARENT_BITMAP_URI =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
Expand Down
61 changes: 61 additions & 0 deletions packages/rn-tester/js/examples/Image/ImageExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import type {ImageProps} from 'react-native/Libraries/Image/ImageProps';
import type {LayoutEvent} from 'react-native/Libraries/Types/CoreEventTypes';

import RNTesterButton from '../../components/RNTesterButton';
import RNTesterText from '../../components/RNTesterText';
import ImageCapInsetsExample from './ImageCapInsetsExample';
import React from 'react';
Expand Down Expand Up @@ -626,6 +627,51 @@ class VectorDrawableExample extends React.Component<
}
}

function CacheControlAndroidExample(): React.Node {
const [reload, setReload] = React.useState(0);

const onReload = () => {
setReload(prevReload => prevReload + 1);
};

return (
<>
<View style={styles.horizontal}>
<View>
<RNTesterText style={styles.resizeModeText}>Default</RNTesterText>
<Image
source={{
uri: fullImage.uri + '?cacheBust=default',
cache: 'default',
}}
style={styles.base}
key={reload}
/>
</View>
<View style={styles.leftMargin}>
<RNTesterText style={styles.resizeModeText}>Reload</RNTesterText>
<Image
source={{
uri: fullImage.uri + '?cacheBust=reload',
cache: 'reload',
}}
style={styles.base}
key={reload}
/>
</View>
</View>

<View style={styles.horizontal}>
<View style={styles.cachePolicyAndroidButtonContainer}>
<RNTesterButton onPress={onReload}>
Re-render image components
</RNTesterButton>
</View>
</View>
</>
);
}

const fullImage: ImageSource = {
uri: IMAGE2,
};
Expand Down Expand Up @@ -863,6 +909,11 @@ const styles = StyleSheet.create({
height: 100,
width: '500%',
},
cachePolicyAndroidButtonContainer: {
flex: 1,
alignItems: 'center',
marginTop: 10,
},
});

exports.displayName = (undefined: ?string);
Expand Down Expand Up @@ -1038,6 +1089,16 @@ exports.examples = [
},
platform: 'ios',
},
{
title: 'Cache Policy',
description: ('First image will be loaded and will be cached. ' +
'Second image is the same but will be reloaded if re-rendered ' +
'as the cache policy is set to reload.': string),
render: function (): React.Node {
return <CacheControlAndroidExample />;
},
platform: 'android',
},
{
title: 'Borders',
name: 'borders',
Expand Down
Loading